.NET陷阱 四 事件监听带来的问题与弱监听器2013-05-21大家可能都遇到过没有取消事件监听而带来的一些问题,像内存泄露、访问无效数据等。当我们写下如下代码时:source.StateChanged += observer.SourceStateChangedHandler实际上source会保持有对observer的一个引用,所以如果source的生命期长于observer的话,则当其它地方不引用observer时,如果不显示解除监听,则observer不会被垃圾回收。这可能会带来两个问题:其一,如果observer占用了大量内存的话,则这部分内存不会被释放;其二,程序的其它地方可能已经处于不一致的状态,这样当source.StateChanged事件再次发生时,observer.SourceStateChanged方法仍然会被调用,而此方法内部的逻辑可能会造成异常。当然最直接的办法是在不使用observer时显示解除监听,像下面那样:source.StateChanged -= observer.SourceStateChangedHandler但程序员经常会忘记这一点。所以便有了“弱事件监听器”的概念,我们期望在监听时多做些工作,然后能达到自动取消监听的目的。废话不说,先上代码。
/// <summary>/// 当弱监听器发现被包装的监听者已经被垃圾收集时所调用的委托。/// </summary>/// <typeparam name="E">事件参数类型。</typeparam>/// <param name="handler">MakeWeak方法返回的事件处理函数,提供此委托的地方/// 要负责把此对象从被监听对象的事件处理方法列表中移除。</param>/// <param name="param">在调用MakeWeak方法时传入的额外参数。</param>public delegate void UnregisterCallback<E>(EventHandler<E> handler, object param) where E : EventArgs;/// <summary>/// 当进行事件处理时,如果被监听对象的生命期比监听器的生命周期长,我们就必/// 须在监听器的生命期内取消对被监听对象的事件监听,否则被监听对象会持有监/// 听器的一个强引用,而阻止它被垃圾收集。但有时我们经常忘记取消事件监听,/// 或者不容易确定何时解除监听。此时可以使用弱监听器,把如下代码:/// <code>/// observed.SomeEvent += observer.SomeEventHandler;/// </code>/// 改为:/// <code>/// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(/// observer.SomeEventHandler, /// (handler, param) => observed.SomeEvent -= handler, /// null);/// </code>/// 上面的代码使用了lambda表达式以捕获变量,也可以像下面那样使用param参数:/// <code>/// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(/// observer.SomeEventHandler, /// OnUnregisterWeakEvent, /// observed);/// /// void OnUnregisterWeakEvent(EventHandler<E> handler, object param)/// {/// ((ObservedType)param).SomeEvent -= handler;/// }/// </code>/// 或者使用如下形式:/// <code>/// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2(/// observer.SomeEventHandler, observed, "SomeEvent");/// </code>/// 其中MakeWeak的第二个参数将弱监听器从事件源中移除。即使将第二个参数指定/// 为null,也不会阻止observer对象被垃圾收集,但事件源中将始终保持一个轻量/// 对象的引用。 /// </summary>public static class WeakEventHandlerFactory{/// <summary>/// 我们在MakeWeak方法中使用反射创建WeakEventHandler的实例,所以在(1)/// 处理无法指定泛型参数T,以完成转换,此接口用于简化这一步骤。/// </summary>/// <typeparam name="E">事件参数类型。</typeparam>private interface IWeakEventHandler<E> where E : EventArgs{/// <summary>/// 事件处理器。/// </summary>EventHandler<E> Handler{get;}}/// <summary>/// 对指定的事件处理函数创建一个弱监听器。/// </summary>/// <typeparam name="E">事件参数类型。</typeparam>/// <param name="handler">被包装的事件处理器。</param>/// <param name="unregister">用于将弱监听器从事件源中移除的委托。可以指/// 定为null,这时事件源中将始终保持一个轻量对象的引用,但不会阻止被包/// 装的对象被垃圾收集。</param>/// <param name="param">在调用unregister时使用的额外参数,可以是null。</param>/// <returns>生成的弱监听器。</returns>public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler,UnregisterCallback<E> unregister, object param) where E : EventArgs{if (handler == null){throw new ArgumentNullException("handler");}if (handler.Method.IsStatic || handler.Target == null){throw new ArgumentException("Only instance methods are supported.", "handler");}var type = typeof(WeakEventHandler<,>).MakeGenericType(handler.Method.DeclaringType, typeof(E));var wehConstructor = type.GetConstructor( new[]{ typeof(EventHandler<E>), typeof(UnregisterCallback<E>),typeof(object)});// (1)var weak = (IWeakEventHandler<E>)wehConstructor.Invoke(new [] { handler, unregister, param });return weak.Handler;}/// <summary>/// 此方法相当于MakeWeak(handler, unregister, null)。/// </summary>public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler,UnregisterCallback<E> unregister) where E : EventArgs{return MakeWeak(handler, unregister, (object)null);}/// <summary>/// 使用CreateUnregisterCallback创建取消弱监听器委托的形式注册监听器。/// </summary>/// <typeparam name="E">事件参数类型。</typeparam>/// <param name="handler">被包装的事件处理器。</param>/// <param name="observed">被监听的对象。</param>/// <param name="eventName">监听的事件名称。</param>/// <returns>生成的弱监听器。</returns>public static EventHandler<E> MakeWeak2<E>(EventHandler<E> handler,object observed, string eventName) where E : EventArgs{return MakeWeak(handler, CreateUnregisterCallback<E>(observed, eventName));}/// <summary>/// 创建一个用于取消弱监听器注册的委托。/// </summary>/// <typeparam name="E">事件参数类型。</typeparam>/// <param name="observed">被监听的对象。</param>/// <param name="eventName">监听的事件名称。</param>/// <returns>创建结果,不会是null。</returns>public static UnregisterCallback<E> CreateUnregisterCallback<E>(object observed, string eventName) where E : EventArgs{return new UnregisterHelper<E>(observed, eventName).Callback;}/// <summary>/// 用于将弱监听器从事件源中移除的辅助类,在C++/CLI等不支持lambda表示式/// 和自动委托的语言中,使用弱监听器的语法可能很复杂,此类用于简化这种/// 情况。/// </summary>/// <typeparam name="E">委托事件参数类型。</typeparam>private class UnregisterHelper<E> where E : EventArgs{/// <summary>/// 被监听的对象。/// </summary>private readonly object observed;/// <summary>/// 事件名称。/// </summary>private readonly string eventName;/// <summary>/// 构造函数。/// </summary>internal UnregisterHelper(object observed, string eventName){this.observed = observed;this.eventName = eventName;}/// <summary>/// 用于取消监听的委托。/// </summary>internal UnregisterCallback<E> Callback{get{return (handler, param) =>{var info = observed.GetType().GetEvent(eventName);info.RemoveEventHandler(observed, handler);};}}}/// <summary>/// 弱事件监听器。/// </summary>/// <typeparam name="T">监听者的类型。</typeparam>/// <typeparam name="E">事件参数类型。</typeparam>private class WeakEventHandler<T, E> : IWeakEventHandler<E>where T : class where E : EventArgs{ /// <summary>/// 对监听器的弱引用。/// </summary>private readonly WeakReference weakReference;/// <summary>/// 用于调用被包装的监听器的委托。/// </summary>private readonly OpenEventHandler openHandler;/// <summary>/// 调用unregister时的额外参数。/// </summary>private readonly object param;/// <summary>/// 监听器移除委托。/// </summary>private UnregisterCallback<E> unregister;/// <summary>/// 构造函数。/// </summary>/// <param name="handler">被包装的事件处理器。</param>/// <param name="unregister">用于移除弱监听器的代码。</param>/// <param name="param">调用unregister时的额外参数。</param>public WeakEventHandler(EventHandler<E> handler,UnregisterCallback<E> unregister, object param){weakReference = new WeakReference(handler.Target);openHandler = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler), null, handler.Method);Handler = Invoke;this.unregister = unregister;this.param = param;}/// <summary>/// 包装监听器事件处理函数的开放委托类型。/// </summary>private delegate void OpenEventHandler(T @this, object sender, E e);/// <summary>/// <see>IWeakEventHandler.Handler</see>/// </summary>public EventHandler<E> Handler{get;private set;}/// <summary>/// 弱监听器事件处理函数。/// </summary>/// <param name="sender">引发事件的对象。</param>/// <param name="e">事件参数。</param>private void Invoke(object sender, E e){T target = (T)weakReference.Target;if (target != null){openHandler.Invoke(target, sender, e);}else if (unregister != null){unregister(Handler, param);unregister = null;}}}}