这是一个旧帖子,我明白了。但是,特别是接受的答案提供的解释不是很准确,其含义是错误的。
抽象的
事先,这不是真正的内存泄漏。特殊绑定引擎对未实现的集合INotifyCollectionChanged
及其关联的生命周期管理CollectionView
会妥善处理分配的内存。
WPF 支持绑定到许多不同的类型DataTable
,例如 XML 或通常实现的类型IList
,IEnumerable
或IListSource
. 如果这是一个严重的错误,那么所有这些绑定都是危险的。
微软会在他们的文档中传播警告,例如,DataTable
在事件或数据绑定的上下文中发生潜在内存泄漏的情况下绑定到。
确实,当绑定到类型的集合时可以避免这种特殊行为,或者通过避免为未实现的集合INotifyCollectionChanged
创建 a :
观察到的行为实际上是由绑定引擎的实际管理而不是数据引起的绑定自己。CollectionView
INotifyCollectionChanged
CollectionView
以下代码触发与绑定 a 相同的行为List\<T>
:
var list = new List<int> {1, 2, 3};
ICollectionView listView = CollectionViewSource.GetDefaultView(list);
list = null;
listView = null;
for (int i = 0; i < 4; i++)
{
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
}
结果:整个集合参考图和CollectionView
仍然在内存中(见下面的解释)。
这应该证明该行为不是由数据绑定引入的,而是由绑定引擎的CollectionView
管理引入的。
数据绑定上下文中的内存泄漏
数据绑定的内存泄漏问题与属性的类型无关,而与绑定源实现的通知系统有关。
源必须
a) 参与依赖属性系统(通过扩展DependencyObject
和实现属性为DependencyProperty
)或
b) 实现INotifyPropertyChanged
否则,绑定引擎将创建对源的静态引用。静态引用是根引用。由于它们在应用程序生命周期内可访问的性质,此类根引用,如静态字段和它们引用的每个对象(内存),将永远不会有资格进行垃圾收集,从而造成内存泄漏。
收藏和CollectionView
管理
收藏是一个不同的故事。所谓的泄漏的原因不是数据绑定本身。绑定引擎也负责创建CollectionView
实际集合。
无论CollectionView
是在绑定的上下文中还是在调用时CollectionViewSource.GetDefaultView
创建:它是创建和管理视图的绑定引擎。
集合和之间的关系CollectionView
是一种单向依赖关系,其中CollectionView
集合知道集合以便自身同步,而集合不知道集合CollectionView
。
每个现有CollectionView
的都由 管理ViewManager
,它是绑定引擎的一部分。为了提高性能,视图管理器缓存视图:它将它们存储在ViewTable
usingWeakReference
中以允许它们被垃圾收集。
当一个集合实现INotifyCollectionChanged
│══════ strong reference R1.1 via event handler ═══════▶⎹
Collection │ │ CollectionView
│◀═══ strong reference R1.2 for lifetime management ═══⎹ ̲ ̲
△
│
│
ViewTable │───── weak reference W1 ──────┘
如果此集合实现,则它本身是来自底层源集合的CollectionView
强引用R1.1INotifyCollectionChanged
的目标。
这个强引用R1.1在CollectionView
它观察到INotifyCollectionChanged.CollectionChanged
事件时创建(通过附加集合存储的事件回调以便在引发事件时调用它)。
这样, 的生命CollectionView
周期与集合的生命周期相耦合:即使应用程序没有对 a 的引用CollectionView
,由于这种强引用,生命周期CollectionView
也会延长,直到集合本身符合垃圾回收条件。
由于CollectionView
实例存储在ViewTable
as WeakReference
W1中,因此这种生命周期耦合可防止WeakReference
W1过早地收集垃圾。
换句话说,这种强耦合R1.1防止了在收集之前CollectionView
被垃圾收集。
此外,管理器还必须保证,只要CollectionView
应用程序引用了 ,即使不再引用该集合,底层集合也将继续存在。这是通过保持对源集合的强参考R1.2来实现的。
无论集合类型如何,此引用始终存在。CollectionView
当一个集合没有实现时INotifyCollectionChanged
Collection │◀═══ strong reference R2.1 for lifetime management ════│ CollectionView
̲ ̲
▲
║
║
ViewTable │════ strong reference R2.2 ═════╝
现在,当集合没有实现时INotifyCollectionChanged
,从集合到所需的强引用CollectionView
不存在(因为不涉及事件处理程序)并且WeakReference
存储在ViewTable
to 中CollectionView
可能会过早地被垃圾回收。
要解决此问题,视图管理器必须CollectionView
“人为地”保持活动状态。
它通过将强参考R2.2存储到CollectionView
. 此时,视图管理器已经存储了一个强引用R2.2 (由于CollectionView
缺少INotifyCollectionChanged
),而这CollectionView
有一个强引用R2.1到底层集合。
这导致视图管理器保持CollectionView
活动状态(R2.2),因此CollectionView
使底层集合保持活动状态( R2.1):这是感知内存泄漏的原因。
但这并不是真正的泄漏,因为视图管理器通过将强引用 R2.2 注册为到期日期来控制强引用R2.2的生命周期。每次访问.CollectionView
CollectionView
视图管理器现在偶尔会在它们的到期日期到期时清除这些引用。CollectionView
最后,当应用程序没有引用这些引用(由垃圾收集器确保)并且不再引用底层集合(由垃圾收集器确保)时,这些引用将被收集。
引入此行为是为了在避免泄漏的同时允许强参考R2.2 。
结论
由于CollectionView
未实现的集合的a 的特殊生命周期管理(使用到期日期) INotifyCollectionChanged
,因此CollectionView
保持活动(在内存中)的时间要长得多。并且由于CollectionView
总体上对其源集合具有强引用,因此该集合及其项目以及所有可访问的引用也保持活动更长的时间。
如果集合已经实现INotifyCollectionChanged
,那么视图管理器将不会存储对 的强引用CollectionView
,因此CollectionView
当它不再被引用并且源集合变得无法访问时,它就会被垃圾回收。
重要的一点是,强引用的生命周期CollectionView
由ViewManager
ie 绑定引擎管理。由于管理算法(到期日期和偶尔清除),此生命周期显着延长。
因此,在对集合及其视图的所有引用都被销毁后观察持久分配的内存是具有欺骗性的。这不是真正的内存泄漏。