大家可能都遇到過沒有取消事件監聽而帶來的一些問題,像內存泄露、訪問無效數據等。當我們寫下如下代碼時:
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。