弱式事件模式

在應用程式中,附加至事件來源的處理程式可能不會與附加至來源的接聽程式物件協調終結。 這種情況可能會導致記憶體流失。 Windows Presentation Foundation (WPF) 引進了一種設計模式,可用來解決此問題,方法是為特定事件提供專用的管理員類別,並在該事件的接聽程式上實作介面。 此設計模式稱為 弱式事件模式

為什麼要實作弱式事件模式?

接聽事件可能會導致記憶體流失。 接聽事件的一般技巧是使用將處理程式附加至來源上事件的語言特定語法。 例如,在 C# 中,該語法為: source.SomeEvent += new SomeEventHandler(MyEventHandler)

這項技術會從事件來源到事件接聽程式建立強式參考。 一般而言,附加接聽程式的事件處理程式會導致接聽程式具有受來源物件存留期影響的物件存留期(除非明確移除事件處理程式)。 但在某些情況下,您可能會希望接聽程式的物件存留期受其他因素控制,例如它目前是否屬於應用程式的可視化樹狀結構,而不是由來源的存留期控制。 每當來源物件存留期超出接聽程式的物件存留期時,一般事件模式會導致記憶體流失:接聽程式保持運作的時間比預期還長。

弱式事件模式的設計目的是要解決此記憶體流失問題。 每當接聽程式需要註冊事件時,就可以使用弱式事件模式,但接聽程式不會明確知道何時要取消註冊。 每當來源的物件存留期超過接聽程式的有用物件存留期時,也可以使用弱式事件模式。 (在此情況下, 有用的 是由您決定的。弱式事件模式可讓接聽程式註冊並接收事件,而不會影響接聽程序的物件存留期特性。 實際上,來源的隱含參考不會判斷接聽程式是否符合垃圾收集的資格。 參考是弱式參考,因此命名弱式事件模式和相關 API。 接聽程式可以垃圾收集或終結,而且來源可以繼續,而不保留目前終結物件的不可回收處理程序參考。

神秘 應該實作弱式事件模式嗎?

實作弱式事件模式主要適用於控件作者。 身為控件作者,您主要負責控件的行為和內含,以及它對於插入控件的應用程式的影響。 這包括控件物件存留期行為,特別是所描述記憶體流失問題的處理。

某些案例原本就適合使用弱式事件模式的應用程式。 其中一種案例是數據系結。 在數據系結中,來源物件通常與接聽程序物件完全無關,這是系結的目標。 WPF 數據系結的許多層面,在實作事件的方式中已套用弱式事件模式。

如何實作弱式事件模式

有三種方式可以實作弱式事件模式。 下表列出這三種方法,並提供一些指引,說明何時應該使用每個方法。

方法 實作時機
使用現有的弱式事件管理員類別 如果您要訂閱的事件具有對應的 WeakEventManager,請使用現有的弱式事件管理員。 如需 WPF 隨附的弱式事件管理員清單,請參閱 類別中的 WeakEventManager 繼承階層。 因為包含的弱式事件管理員有限,因此您可能需要選擇其中一個其他方法。
使用泛型弱式事件管理員類別 當無法使用現有WeakEventManager時,請使用泛型WeakEventManager<TEventSource,TEventArgs>,您想要簡單的方式來實作,而且您不關心效率。 泛型 WeakEventManager<TEventSource,TEventArgs> 比現有或自定義弱式事件管理員低。 例如,泛型類別會執行更多反映,以在事件名稱指定的情況下探索事件。 此外,使用泛型 WeakEventManager<TEventSource,TEventArgs> 註冊事件的程式代碼比使用現有或自定義 WeakEventManager更詳細。
建立自定義弱式事件管理員類別 當現有WeakEventManager無法使用且您想要獲得最佳效率時,請建立自定義WeakEventManager。 使用自訂 WeakEventManager 訂閱事件會更有效率,但您確實會產生在開頭撰寫更多程序代碼的成本。
使用第三方弱式事件管理員 NuGet 有 數個弱式事件管理員 ,許多 WPF 架構也支援模式。

下列各節說明如何實作弱式事件模式。 為了進行此討論,要訂閱的事件具有下列特性。

  • 事件名稱稱為 SomeEvent

  • 事件是由 EventSource 類別引發。

  • 事件處理程式的類型為: SomeEventEventHandler (或 EventHandler<SomeEventEventArgs>)。

  • 事件會將 類型的 SomeEventEventArgs 參數傳遞至事件處理程式。

使用現有的 Weak 事件管理員類別

  1. 尋找現有的弱式事件管理員。

    如需 WPF 隨附的弱式事件管理員清單,請參閱 類別中的 WeakEventManager 繼承階層。

  2. 使用新的弱式事件管理員,而不是一般事件連結。

    例如,如果您的程式代碼使用下列模式來訂閱事件:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    將它變更為下列模式:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    同樣地,如果您的程式代碼使用下列模式取消訂閱事件:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    將它變更為下列模式:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

使用泛型 Weak 事件管理員類別

  1. 使用泛型 WeakEventManager<TEventSource,TEventArgs> 類別,而不是一般事件連結。

    當您使用 WeakEventManager<TEventSource,TEventArgs> 來註冊事件接聽程式時,您會提供事件來源和 EventArgs 類型做為 類別的類型參數,並呼叫 AddHandler ,如下列程式代碼所示:

    WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
    

建立自定義 Weak 事件管理員類別

  1. 將下列類別範本複製到您的專案。

    這個類別繼承自 WeakEventManager 類別。

    class SomeEventWeakEventManager : WeakEventManager
    {
    
        private SomeEventWeakEventManager()
        {
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(EventSource source,
                                      EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(EventSource source,
                                         EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedRemoveHandler(source, handler);
        }
    
        /// <summary>
        /// Get the event manager for the current thread.
        /// </summary>
        private static SomeEventWeakEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(SomeEventWeakEventManager);
                SomeEventWeakEventManager manager =
                    (SomeEventWeakEventManager)GetCurrentManager(managerType);
    
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new SomeEventWeakEventManager();
                    SetCurrentManager(managerType, manager);
                }
    
                return manager;
            }
        }
    
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<SomeEventEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
  2. SomeEventWeakEventManager 名稱取代為您自己的名稱。

  3. 以您事件的對應名稱取代先前所述的三個名稱。 (SomeEventEventSourceSomeEventEventArgs

  4. 將弱式事件管理員類別的可見性(public/internal/private)設定為與所管理之事件相同的可見度。

  5. 使用新的弱式事件管理員,而不是一般事件連結。

    例如,如果您的程式代碼使用下列模式來訂閱事件:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    將它變更為下列模式:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    同樣地,如果您的程式代碼使用下列模式取消訂閱事件:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    將它變更為下列模式:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

另請參閱