這其實是<使用 Weak Events 來避免記憶體洩漏>的續集,主要是補充上次略過的部分,也就是撰寫自訂的 WeakEventManager 類別。此類別與我們的程式有何關係,前因後果已在上集說過,這裡就不再重複。
上次的範例程式共有三個,分別示範了事件訂閱所造成的 memory leak 問題,以及兩種解法。文中也已經提供了這三個範例程式的原始碼下載連結。底下的範例程式係針對先前的第三個範例稍作修改,因此,如果沒有看過前文,可能不易看懂這裡的程式碼在做什麼。
第三個範例是用 WeakEventManager<TEventSource, TEventArgs> 來建立 weak event handler,這種解法非常省事,一兩行程式碼就解決了。但如果你非常在意泛型所帶來的額外效率損耗,還有另一種選擇,就是撰寫自訂的 WeakEventManager 類別。
直接修改先前的第三個範例(專案名稱是 WeakEventDemoWpf),首先加入一個類別: ButtonClickEventManager。此類別必須繼承自抽象類別 WeakEventManager,程式碼如下:
這裡唯一用到泛型的地方,是事件處理常式的參數型別:RoutedEventArgs。此型別在上面的程式碼中出現好幾次,它並不是隨意挑選的--你的事件處理常式需要什麼事件參數型別,這裡就用什麼型別。
靜態方法 AddHandler 和 RemoveHandler 是用來供外界訂閱和移除事件處理常式。它們都需要兩個參數,用來指名事件來源是誰,以及一個代表事件處理常式的委派物件。
在 AddHandler 方法中呼叫的 CurrentManager.ProtectedAddHandler() 會再去呼叫你改寫的 StartListening 方法,以完成事件訂閱的動作。RemoveHandler 方法在移除事件處理常式時也是類似的過程。
最後一個要注意的是,此衍生類別必須改寫三個方法:NewListenerList、StartListening、和 StopListening。少了第一個方法,執行時會出現事件參數型別不符的錯誤;少了後面兩個方法,則無法通過編譯。
寫好這個類別之後,找到原先範例中的 MainWindow.xaml.cs 中的 btnRegisterEvent_Click 事件處理常式:
把它改成:
這樣就行了。收工!
喔對了,.NET Framework 4.5 已經預先提供了幾個比較常用的 WeakEventManager 具象類別:
System.Collections.Specialized.CollectionChangedEventManager
System.ComponentModel.CurrentChangedEventManager
System.ComponentModel.CurrentChangingEventManager
System.ComponentModel.ErrorsChangedEventManager
System.ComponentModel.PropertyChangedEventManager
System.Windows.Data.DataChangedEventManager
System.Windows.Input.CanExecuteChangedEventManager
System.Windows.LostFocusEventManager
System.Windows.WeakEventManager<TEventSource, TEventArgs>
在捲起袖子之前,先看看裡面有沒有合用的吧!(最後一個就是先前介紹過的泛型 WeakEventManager 類別)
延伸閱讀
上次的範例程式共有三個,分別示範了事件訂閱所造成的 memory leak 問題,以及兩種解法。文中也已經提供了這三個範例程式的原始碼下載連結。底下的範例程式係針對先前的第三個範例稍作修改,因此,如果沒有看過前文,可能不易看懂這裡的程式碼在做什麼。
第三個範例是用 WeakEventManager<TEventSource, TEventArgs> 來建立 weak event handler,這種解法非常省事,一兩行程式碼就解決了。但如果你非常在意泛型所帶來的額外效率損耗,還有另一種選擇,就是撰寫自訂的 WeakEventManager 類別。
直接修改先前的第三個範例(專案名稱是 WeakEventDemoWpf),首先加入一個類別: ButtonClickEventManager。此類別必須繼承自抽象類別 WeakEventManager,程式碼如下:
using System.Windows; using System.Windows.Controls; namespace WeakEventDemoWpf { public class ButtonClickEventManager : WeakEventManager { public static ButtonClickEventManager CurrentManager { get { var manager_type = typeof(ButtonClickEventManager); var manager = WeakEventManager.GetCurrentManager(manager_type) as ButtonClickEventManager; if (manager == null) { manager = new ButtonClickEventManager(); WeakEventManager.SetCurrentManager(manager_type, manager); } return manager; } } public static void AddHandler(Button source, EventHandler<RoutedEventArgs> handler) { CurrentManager.ProtectedAddHandler(source, handler); } public static void RemoveHandler(Button source, EventHandler<RoutedEventArgs> handler) { CurrentManager.ProtectedRemoveHandler(source, handler); } /// <summary> /// Return a new list to hold listeners to the event. /// </summary> protected override ListenerList NewListenerList() { return new ListenerList<RoutedEventArgs>(); } protected override void StartListening(object source) { ((Button)source).Click += DeliverEvent; } protected override void StopListening(object source) { ((Button)source).Click -= DeliverEvent; } } }
這裡唯一用到泛型的地方,是事件處理常式的參數型別:RoutedEventArgs。此型別在上面的程式碼中出現好幾次,它並不是隨意挑選的--你的事件處理常式需要什麼事件參數型別,這裡就用什麼型別。
靜態方法 AddHandler 和 RemoveHandler 是用來供外界訂閱和移除事件處理常式。它們都需要兩個參數,用來指名事件來源是誰,以及一個代表事件處理常式的委派物件。
在 AddHandler 方法中呼叫的 CurrentManager.ProtectedAddHandler() 會再去呼叫你改寫的 StartListening 方法,以完成事件訂閱的動作。RemoveHandler 方法在移除事件處理常式時也是類似的過程。
最後一個要注意的是,此衍生類別必須改寫三個方法:NewListenerList、StartListening、和 StopListening。少了第一個方法,執行時會出現事件參數型別不符的錯誤;少了後面兩個方法,則無法通過編譯。
寫好這個類別之後,找到原先範例中的 MainWindow.xaml.cs 中的 btnRegisterEvent_Click 事件處理常式:
private void btnRegisterEvent_Click(object sender, RoutedEventArgs e) { ListenerWindow aWindow = new ListenerWindow(); //標準 .NET 事件訂閱的寫法: //btnTest.Click += aWindow.ShowTime; // 使用 WeakEventManager<Button, RoutedEventArgs> 的寫法: EventHandler<RoutedEventArgs> handler = new EventHandler<RoutedEventArgs>(aWindow.ShowTime); WeakEventManager<Button, RoutedEventArgs>.AddHandler(btnTest, "Click", handler); aWindow.Show(); ShowMemoryUsed(); }
把它改成:
private void btnRegisterEvent_Click(object sender, RoutedEventArgs e) { ListenerWindow aWindow = new ListenerWindow(); // 使用自訂 WeakEventManager 類別: ButtonClickEventManager.AddHandler(btnTest, new EventHandler<RoutedEventArgs>(aWindow.ShowTime)); aWindow.Show(); ShowMemoryUsed(); }
這樣就行了。收工!
喔對了,.NET Framework 4.5 已經預先提供了幾個比較常用的 WeakEventManager 具象類別:
System.Collections.Specialized.CollectionChangedEventManager
System.ComponentModel.CurrentChangedEventManager
System.ComponentModel.CurrentChangingEventManager
System.ComponentModel.ErrorsChangedEventManager
System.ComponentModel.PropertyChangedEventManager
System.Windows.Data.DataChangedEventManager
System.Windows.Input.CanExecuteChangedEventManager
System.Windows.LostFocusEventManager
System.Windows.WeakEventManager<TEventSource, TEventArgs>
在捲起袖子之前,先看看裡面有沒有合用的吧!(最後一個就是先前介紹過的泛型 WeakEventManager 類別)
延伸閱讀
沒有留言: