.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&lt;E&gt; 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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM