WPF 4.5:撰寫自訂的 WeakEventManager 類別

這其實是<使用 Weak Events 來避免記憶體洩漏>的續集,主要是補充上次略過的部分,也就是撰寫自訂的 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 類別)

延伸閱讀

Post Comments

技術提供:Blogger.