.NET陷阱之四:事件监听带来的问题与弱监听器
大家可能都遇到过没有取消事件监听而带来的一些问题,像内存泄露、访问无效数据等。当我们写下如下代码时:
source.StateChanged += observer.SourceStateChangedHandler
实际上source会保持有对observer的一个引用,所以如果source的生命期长于observer的话,则当其它地方不引用observer时,如果不显示解除监听,则observer不会被垃圾回收。这可能会带来两个问题:其一,如果observer占用了大量内存的话,则这部分内存不会被释放;其二,程序的其它地方可能已经处于不一致的状态,这样当source.StateChanged事件再次发生时,observer.SourceStateChanged方法仍然会被调用,而此方法内部的逻辑可能会造成异常。
当然最直接的办法是在不使用observer时显示解除监听,像下面那样:
source.StateChanged -= observer.SourceStateChangedHandler
但程序员经常会忘记这一点。所以便有了“弱事件监听器”的概念,我们期望在监听时多做些工作,然后能达到自动取消监听的目的。废话不说,先上代码。
1 /// <summary> 2 /// 当弱监听器发现被包装的监听者已经被垃圾收集时所调用的委托。 3 /// </summary> 4 /// <typeparam name="E">事件参数类型。</typeparam> 5 /// <param name="handler">MakeWeak方法返回的事件处理函数,提供此委托的地方 6 /// 要负责把此对象从被监听对象的事件处理方法列表中移除。</param> 7 /// <param name="param">在调用MakeWeak方法时传入的额外参数。</param> 8 public delegate void UnregisterCallback<E>(EventHandler<E> handler, object param) where E : EventArgs; 9 10 /// <summary> 11 /// 当进行事件处理时,如果被监听对象的生命期比监听器的生命周期长,我们就必 12 /// 须在监听器的生命期内取消对被监听对象的事件监听,否则被监听对象会持有监 13 /// 听器的一个强引用,而阻止它被垃圾收集。但有时我们经常忘记取消事件监听, 14 /// 或者不容易确定何时解除监听。此时可以使用弱监听器,把如下代码: 15 /// <code> 16 /// observed.SomeEvent += observer.SomeEventHandler; 17 /// </code> 18 /// 改为: 19 /// <code> 20 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak( 21 /// observer.SomeEventHandler, 22 /// (handler, param) => observed.SomeEvent -= handler, 23 /// null); 24 /// </code> 25 /// 上面的代码使用了lambda表达式以捕获变量,也可以像下面那样使用param参数: 26 /// <code> 27 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak( 28 /// observer.SomeEventHandler, 29 /// OnUnregisterWeakEvent, 30 /// observed); 31 /// 32 /// void OnUnregisterWeakEvent(EventHandler<E> handler, object param) 33 /// { 34 /// ((ObservedType)param).SomeEvent -= handler; 35 /// } 36 /// </code> 37 /// 或者使用如下形式: 38 /// <code> 39 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2( 40 /// observer.SomeEventHandler, observed, "SomeEvent"); 41 /// </code> 42 /// 其中MakeWeak的第二个参数将弱监听器从事件源中移除。即使将第二个参数指定 43 /// 为null,也不会阻止observer对象被垃圾收集,但事件源中将始终保持一个轻量 44 /// 对象的引用。 45 /// </summary> 46 public static class WeakEventHandlerFactory 47 { 48 /// <summary> 49 /// 我们在MakeWeak方法中使用反射创建WeakEventHandler的实例,所以在(1) 50 /// 处理无法指定泛型参数T,以完成转换,此接口用于简化这一步骤。 51 /// </summary> 52 /// <typeparam name="E">事件参数类型。</typeparam> 53 private interface IWeakEventHandler<E> where E : EventArgs 54 { 55 /// <summary> 56 /// 事件处理器。 57 /// </summary> 58 EventHandler<E> Handler 59 { 60 get; 61 } 62 } 63 64 /// <summary> 65 /// 对指定的事件处理函数创建一个弱监听器。 66 /// </summary> 67 /// <typeparam name="E">事件参数类型。</typeparam> 68 /// <param name="handler">被包装的事件处理器。</param> 69 /// <param name="unregister">用于将弱监听器从事件源中移除的委托。可以指 70 /// 定为null,这时事件源中将始终保持一个轻量对象的引用,但不会阻止被包 71 /// 装的对象被垃圾收集。</param> 72 /// <param name="param">在调用unregister时使用的额外参数,可以是null。</param> 73 /// <returns>生成的弱监听器。</returns> 74 public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler, 75 UnregisterCallback<E> unregister, object param) where E : EventArgs 76 { 77 if (handler == null) 78 { 79 throw new ArgumentNullException("handler"); 80 } 81 82 if (handler.Method.IsStatic || handler.Target == null) 83 { 84 throw new ArgumentException("Only instance methods are supported.", "handler"); 85 } 86 87 var type = typeof(WeakEventHandler<,>).MakeGenericType( 88 handler.Method.DeclaringType, typeof(E)); 89 90 var wehConstructor = type.GetConstructor( new[] 91 { 92 typeof(EventHandler<E>), 93 typeof(UnregisterCallback<E>), 94 typeof(object) 95 }); 96 97 // (1) 98 var weak = (IWeakEventHandler<E>)wehConstructor.Invoke( 99 new [] { handler, unregister, param }); 100 return weak.Handler; 101 } 102 103 /// <summary> 104 /// 此方法相当于MakeWeak(handler, unregister, null)。 105 /// </summary> 106 public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler, 107 UnregisterCallback<E> unregister) where E : EventArgs 108 { 109 return MakeWeak(handler, unregister, (object)null); 110 } 111 112 /// <summary> 113 /// 使用CreateUnregisterCallback创建取消弱监听器委托的形式注册监听器。 114 /// </summary> 115 /// <typeparam name="E">事件参数类型。</typeparam> 116 /// <param name="handler">被包装的事件处理器。</param> 117 /// <param name="observed">被监听的对象。</param> 118 /// <param name="eventName">监听的事件名称。</param> 119 /// <returns>生成的弱监听器。</returns> 120 public static EventHandler<E> MakeWeak2<E>(EventHandler<E> handler, 121 object observed, string eventName) where E : EventArgs 122 { 123 return MakeWeak(handler, CreateUnregisterCallback<E>(observed, eventName)); 124 } 125 126 /// <summary> 127 /// 创建一个用于取消弱监听器注册的委托。 128 /// </summary> 129 /// <typeparam name="E">事件参数类型。</typeparam> 130 /// <param name="observed">被监听的对象。</param> 131 /// <param name="eventName">监听的事件名称。</param> 132 /// <returns>创建结果,不会是null。</returns> 133 public static UnregisterCallback<E> CreateUnregisterCallback<E>( 134 object observed, string eventName) where E : EventArgs 135 { 136 return new UnregisterHelper<E>(observed, eventName).Callback; 137 } 138 139 /// <summary> 140 /// 用于将弱监听器从事件源中移除的辅助类,在C++/CLI等不支持lambda表示式 141 /// 和自动委托的语言中,使用弱监听器的语法可能很复杂,此类用于简化这种 142 /// 情况。 143 /// </summary> 144 /// <typeparam name="E">委托事件参数类型。</typeparam> 145 private class UnregisterHelper<E> where E : EventArgs 146 { 147 /// <summary> 148 /// 被监听的对象。 149 /// </summary> 150 private readonly object observed; 151 152 /// <summary> 153 /// 事件名称。 154 /// </summary> 155 private readonly string eventName; 156 157 /// <summary> 158 /// 构造函数。 159 /// </summary> 160 internal UnregisterHelper(object observed, string eventName) 161 { 162 this.observed = observed; 163 this.eventName = eventName; 164 } 165 166 /// <summary> 167 /// 用于取消监听的委托。 168 /// </summary> 169 internal UnregisterCallback<E> Callback 170 { 171 get 172 { 173 return (handler, param) => 174 { 175 var info = observed.GetType().GetEvent(eventName); 176 info.RemoveEventHandler(observed, handler); 177 }; 178 } 179 } 180 } 181 182 /// <summary> 183 /// 弱事件监听器。 184 /// </summary> 185 /// <typeparam name="T">监听者的类型。</typeparam> 186 /// <typeparam name="E">事件参数类型。</typeparam> 187 private class WeakEventHandler<T, E> : IWeakEventHandler<E> 188 where T : class where E : EventArgs 189 { 190 /// <summary> 191 /// 对监听器的弱引用。 192 /// </summary> 193 private readonly WeakReference weakReference; 194 195 /// <summary> 196 /// 用于调用被包装的监听器的委托。 197 /// </summary> 198 private readonly OpenEventHandler openHandler; 199 200 /// <summary> 201 /// 调用unregister时的额外参数。 202 /// </summary> 203 private readonly object param; 204 205 /// <summary> 206 /// 监听器移除委托。 207 /// </summary> 208 private UnregisterCallback<E> unregister; 209 210 /// <summary> 211 /// 构造函数。 212 /// </summary> 213 /// <param name="handler">被包装的事件处理器。</param> 214 /// <param name="unregister">用于移除弱监听器的代码。</param> 215 /// <param name="param">调用unregister时的额外参数。</param> 216 public WeakEventHandler(EventHandler<E> handler, 217 UnregisterCallback<E> unregister, object param) 218 { 219 weakReference = new WeakReference(handler.Target); 220 openHandler = (OpenEventHandler)Delegate.CreateDelegate( 221 typeof(OpenEventHandler), null, handler.Method); 222 Handler = Invoke; 223 this.unregister = unregister; 224 this.param = param; 225 } 226 227 /// <summary> 228 /// 包装监听器事件处理函数的开放委托类型。 229 /// </summary> 230 private delegate void OpenEventHandler(T @this, object sender, E e); 231 232 /// <summary> 233 /// <see>IWeakEventHandler.Handler</see> 234 /// </summary> 235 public EventHandler<E> Handler 236 { 237 get; 238 private set; 239 } 240 241 /// <summary> 242 /// 弱监听器事件处理函数。 243 /// </summary> 244 /// <param name="sender">引发事件的对象。</param> 245 /// <param name="e">事件参数。</param> 246 private void Invoke(object sender, E e) 247 { 248 T target = (T)weakReference.Target; 249 if (target != null) 250 { 251 openHandler.Invoke(target, sender, e); 252 } 253 else if (unregister != null) 254 { 255 unregister(Handler, param); 256 unregister = null; 257 } 258 } 259 } 260 }
此工具的具体使用方法可以参考WeakEventFactory的注释。这样通过在添加监听时多写一些代码,就可以避免忘记取消监听或者不知道在什么地方取消监听所带来的问题了。其中第二种方法可以用于C++/CLI中,再加上一些宏定义就非常简洁了。
observed.SomeEvent += WeakEventHandlerFactory.MakeWeak( observer.SomeEventHandler, (handler, param) => observed.SomeEvent -= handler, null); observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2( observer.SomeEventHandler, observed, "SomeEvent");
PS: 弱监听器和上面WeakEventHandlerFactory代码的思想是我在解决此问题时从网上找到的,但没有记下具体的网址,所以这里不能提供相应的链接。大家可以自行在google中搜索weak event listener。