在之前写的一篇文章(XAML: 自界说控件中事件措置惩罚惩罚的最佳实践)中,我们曾提到了在 .NET 中如果事件没有反注册,将会引起内存泄露。这主要是因为当事件源会对事件监听者孕育产生一个强引用,导致事件监听者无法被垃圾回收。
在这篇文章中,我们首先将进一步说明内存泄露的问题;然后,我们会重点介绍 .NET 中的 Weak Event 模型以及它的应用;之所以使用 Weak Event 模型就是为了解决通例事件中所引起的内存泄露;最后,我们会本身来实现 Weak Event 模型。
一、再谈内存泄露 1. 原因我们凡是会这样为事件添加事件监听: <source>.<event> += <listener-delegate> 。这样注册事件会使事件源对事件监听者孕育产生一个强引用(如下图)。即使事件监听者不再使用时,它也无法被垃圾回收,从而引起了内存泄露。
而事件源之所以对事件监听者孕育产生强引用,这是由于事件是基于委托,当为某事件注册了监听时,该事件对应的委托会存储对事件监听者的引用。要解决这个问题,只能通过反注册事件。 2. 具体问题一个具体的例子是,对付 XAML 应用中的数据绑定,我们会为 Model 实现 INotifyPropertyChanged 接口,这个接口里面包罗一个事件:PropertyChanged。当这个事件被触发时,那么暗示属性值产生了转变,这时 UI 上绑定此属性的控件的值也要随着变革。
在这个场景中,Model 作为数据源,而 UI 作为事件监听者。如果凭据通例事件来措置惩罚惩罚 Model 中的 PropertyChanged 事件,那么,Model 就会对 UI 上的控件孕育产生一个强引用。甚至在控件从可视化树 (VisualTree) 上移除后,只要 Model 的生命周期还没结束,,那么控件就必然不能被回收。
可想而之,当 UI 中使用数据绑定的控件在 VisualTree 上经常变革时(添加或移除),造成的内存泄露问题将会非常严重。
因此,WPF 引入了 Weak Event 模式来解决这个问题。
二、Weak Event 模型 1. WeakEventManager 与 IWeakEventListenerWeak Event 模型主要解决的问题就是内存泄露。它通过 WeakEventManager 来实现;WeakEventManager 为作事件源和事件监听者的“中间人”,当事件源的事件触发时,由它卖力向事件监听者通报事件。而 WeakEventManager 对事件监听者的引用是弱引用,因此,并不影响事件监听者被垃圾回收。如下图:
WeakEventManager 是一个抽象类,包罗两个抽象要领和一些受掩护要领,因此要使用它,就需要创建它的派生类。public abstract class WeakEventManager : DispatcherObject { protected static WeakEventManager GetCurrentManager(Type managerType); protected static void SetCurrentManager(Type managerType, WeakEventManager manager); protected void DeliverEvent(object sender, EventArgs args); protected void ProtectedAddHandler(object source, Delegate handler); protected void ProtectedAddListener(object source, IWeakEventListener listener); protected void ProtectedRemoveHandler(object source, Delegate handler); protected void ProtectedRemoveListener(object source, IWeakEventListener listener); protected abstract void StartListening(object source); protected abstract void StopListening(object source); }
除了 WeakEventManager,还要用到 IWeakEventListener 接口,需要措置惩罚惩罚事件的类要实现这个接口,它包罗一个要领:
public interface IWeakEventListener { bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e); }
ReceiveWeakEvent 要领可以得到 EventManager 的类型以及事件源和事件参数,它返回 bool 类型,用于指明通报过来的事件是否被措置惩罚惩罚。
2. WPF 如何解决问题在 WPF 中,对付 INotifyPropertyChanged 接口的 PropertyChanged 事件,以及 INotifyCollectionChanged 接口的 CollectionChanged 事件等,都有对应的 WeakEventManager 来措置惩罚惩罚它们。如下: