这是一个旧帖子,我明白了。但是,特别是接受的答案提供的解释不是很准确,其含义是错误的。
摘要
事先,这不是真正的内存泄漏。对于未实现 INotifyCollectionChanged 及其关联的 CollectionView 的集合,特殊绑定引擎的生命周期管理会妥善处理分配的内存。
WPF 支持绑定到许多不同的类型,例如 DataTable 和 XML,或者通常绑定到实现 IList、IEnumerable 或 IListSource 的类型。如果这是一个严重的错误,那么所有这些绑定都是危险的。
Microsoft 将在其文档中传播警告,例如绑定到 DataTable,就像在事件或数据绑定的上下文中潜在内存泄漏的情况下一样。
确实,当绑定到 INotifyCollectionChanged 类型的集合时,可以避免这种特殊行为 - 或者通过避免为未实现 INotifyCollectionChanged 的集合创建 CollectionView:
观察到的行为实际上是由绑定引擎的实际 CollectionView 管理而不是数据绑定本身引起的。
以下代码触发与绑定到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 时创建:它是创建和管理视图的绑定引擎。
collection和CollectionView的关系是单向依赖,CollectionView知道collection是为了同步自己,而collection不知道CollectionView。
每个现有的CollectionView 都由ViewManager 管理,它是绑定引擎的一部分。为了提高性能,视图管理器缓存视图:它使用WeakReference 将它们存储在ViewTable 中,以允许它们被垃圾回收。
当一个集合实现INotifyCollectionChanged
│══════ strong reference R1.1 via event handler ═══════▶⎹
Collection │ │ CollectionView
│◀═══ strong reference R1.2 for lifetime management ═══⎹ ̲ ̲
△
│
│
ViewTable │───── weak reference W1 ──────┘
如果此集合实现INotifyCollectionChanged,则CollectionView 本身就是来自底层源集合的强引用R1.1 的目标。
这个强引用 R1.1 是由 CollectionView 在观察到 INotifyCollectionChanged.CollectionChanged 事件时创建的(通过附加集合存储的事件回调以便在引发事件时调用它)。
这样,CollectionView 的生命周期与集合的生命周期耦合:即使应用程序没有对 CollectionView 的引用,由于这种强引用,CollectionView 的生命周期会延长到收集本身有资格进行垃圾收集。
由于CollectionView 实例以WeakReference W1 的形式存储在ViewTable 中,因此这种生命周期耦合可防止WeakReference W1 过早地收集垃圾。
换句话说,这种强耦合R1.1 防止CollectionView 在收集之前 被垃圾收集。
此外,管理器还必须保证,只要应用程序引用了CollectionView,即使不再引用该集合,底层集合也会继续存在。这是通过保持从 CollectionView 到源集合的强引用 R1.2 来实现的。
无论集合类型如何,此引用始终存在。
当一个集合没有实现INotifyCollectionChanged
Collection │◀═══ strong reference R2.1 for lifetime management ════│ CollectionView
̲ ̲
▲
║
║
ViewTable │════ strong reference R2.2 ═════╝
现在,当集合没有实现INotifyCollectionChanged,则从集合到CollectionView 所需的强引用不存在(因为不涉及事件处理程序)并且WeakReference 存储在ViewTable 到CollectionView 中可能会过早地进行垃圾收集。
要解决此问题,视图管理器必须“人为地”保持CollectionView 活动。
它通过将强引用 R2.2 存储到CollectionView 来实现这一点。此时,视图管理器存储了对CollectionView 的强引用R2.2(由于缺少INotifyCollectionChanged),而这个CollectionView 具有强引用R2。 1 到基础集合。
这导致视图管理器保持 CollectionView 活动 (R2.2),因此 CollectionView 保持底层集合活动 (R2.1):这是感知到内存泄漏的原因。
但这并不是真正的泄漏,因为视图管理器通过注册强引用 R2.2 控制强引用 R2.2 到 CollectionView 的生命周期em> 有到期日。每次访问CollectionView 时都会更新此日期。
视图管理器现在偶尔会在过期日期到期时清除这些引用。最后,这些参考资料将在以下时间收集
CollectionView 未被应用程序引用(由垃圾收集器确保),并且不再引用底层集合(由垃圾收集器确保)。
引入此行为是为了允许强引用R2.2,同时避免泄漏。
结论
由于未实现INotifyCollectionChanged 的集合的CollectionView 的特殊生命周期管理(使用到期日期),CollectionView 的存活时间(在内存中)要长得多。并且因为 CollectionView 通常对其源集合具有强引用,因此该集合及其项和所有可访问的引用也会保持更长时间。
如果集合实现了INotifyCollectionChanged,那么视图管理器将不会存储对CollectionView 的强引用,因此CollectionView 将在它不再被引用并且源集合的那一刻被垃圾回收变得无法访问。
重要的是,对CollectionView 的强引用的生命周期由ViewManager 即绑定引擎管理。由于管理算法(到期日期和偶尔清除),此生命周期显着延长。
因此,在对集合及其视图的所有引用都被销毁之后,观察持久分配的内存是具有欺骗性的。这不是真正的内存泄漏。