弱式事件模式
在應用程式中,附加至事件來源的處理程式可能不會與附加至來源的接聽程式物件協調終結。 這種情況可能會導致記憶體流失。 Windows Presentation Foundation(WPF)引進了可用來解決此問題的設計模式,該設計模式提供特定事件的專用管理員類別,並在該事件的接聽程式上實作介面。 此設計模式稱為弱式事件模式。
為什麼要實作弱式事件模式?
接聽事件可能會導致記憶體流失。 接聽事件的一般技巧是使用特定語言的語法,將處理程式附加至來源上的事件。 例如,在 C# 中,其語法為: source.SomeEvent += new SomeEventHandler(MyEventHandler)
。
這項技術會從事件來源到事件接聽程式建立起強式參考。 一般而言,附加事件處理程式的接聽程式會導致接聽程式具有受來源物件存留期影響的物件存留期(除非明確移除事件處理程式)。 但在某些情況下,您可能會希望接聽程式的物件存留期會受到其他因素控制,例如它目前是否屬於應用程式的視覺化樹狀結構,而不是由來源的存留期控制。 每當來源的物件存留期超出接聽程式的物件存留期時,一般事件模式會導致記憶體流失:接聽程式保持運作的時間比預期要長。
弱式事件模式的設計目的是要解決記憶體流失問題。 每當接聽程式需要註冊事件時,就可以使用弱式事件模式,但接聽程式不會明確知道何時要取消註冊。 每當來源的物件存留期超過接聽程式的有用物件存留期時,也可以使用弱式事件模式。 (在此情況下, 有用 是由您來決定的。)弱式事件模式可讓接聽程式註冊並接收事件,而不會影響接聽程序的物件存留期特性。 實際上,來自來源的隱含參考無法決定接聽程式是否符合垃圾回收的資格。 此處參考屬於弱式參考,因此命名為弱式事件模式和相關 API。 接聽程式可以垃圾收集或其他方式終結,且來源可在不保留目前已終結物件的不可回收處理程序參考情況下繼續使用。
誰應該要實作弱式事件模式?
實作弱式事件模式主要適用於控制物件作者。 身為控制物件作者,您主要負責控制物件的行為和包含關係,以及它對於被插入控制物件之應用程式的影響。 這包括控制物件的存留期行為,尤其是處理所述的記憶體流失問題。
某些情境原本就適合使用弱式事件模式。 其中一種情境就是資料繫結。 在資料繫結的情境中,來源物件通常完全獨立於作為繫結目標的程式序物件。 WPF 資料繫結的許多層面在事件實作方式中已套用弱式事件模式。
如何實作弱式事件模式
有三種方式可以實作弱式事件模式。 下表列出這三種方法,並提供一些指引,說明每個方法應該於何時使用。
方法 | 何時實作 |
---|---|
使用現有的弱式事件管理員類別 | 如果您要訂閱的事件具有對應的WeakEventManager,請使用現有的弱式事件管理員。 如需 WPF 隨附的弱式事件管理員清單,請參閱WeakEventManager類別中的繼承階層。 因為包含的弱式事件管理員有限,您可能需要選擇其他方法之一。 |
使用通用型的弱式事件管理員類別 | 當現有的WeakEventManager<TEventSource,TEventArgs>無法使用,且您希望以簡單的方式進行實作,又不關心效率時,請使用通用型WeakEventManager。 通用型WeakEventManager<TEventSource,TEventArgs>效率低於現有或自定義弱式事件管理員。 例如,通用型類別會採用更多反射機制,以在給定事件名稱情況下探索事件。 此外,使用通用型WeakEventManager<TEventSource,TEventArgs>註冊事件的程式代碼比使用現有或自定義WeakEventManager更為詳細。 |
建立自定義的弱式事件管理員類別 | 當現有WeakEventManager無法使用且您想要獲得最佳效率時,請建立自定義WeakEventManager。 使用自定義WeakEventManager訂閱事件會更有效率,但您一開始需要付出成本來撰寫更多程式代碼。 |
使用第三方弱式事件管理員 | NuGet 有數個弱式事件管理員,且有許多 WPF 架構也支援此模式。 |
以下各部分將說明如何實作弱式事件模式。 為了進行此討論,要訂閱的事件具有下列特性。
事件名稱為
SomeEvent
。事件是由
EventSource
類別所引發。事件處理程式有著以下類別:
SomeEventEventHandler
(或EventHandler<SomeEventEventArgs>
)。事件會將類型
SomeEventEventArgs
的參數傳遞至事件處理程式。
使用現有的弱式事件管理員類別
查找現有的弱式事件管理員。
如需 WPF 隨附的弱式事件管理員清單,請參閱WeakEventManager類別中的繼承階層。
使用新的弱式事件管理員,而不是一般事件連結。
例如,如果您的程式代碼使用下列模式來訂閱事件:
source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
將其變更為下列模式:
SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
同樣地,如果您的程式代碼使用下列模式來訂閱事件:
source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
將其變更為下列模式:
SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
使用通用型的弱式事件管理員類別
使用通用型WeakEventManager<TEventSource,TEventArgs>類別,而不是一般事件連結。
當您使用WeakEventManager<TEventSource,TEventArgs>註冊事件接聽程式時,您會提供事件來源和EventArgs類型作為該類別的類型參數,並按照下列所示的程式代碼呼叫AddHandler:
WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
建立自定義的弱式事件管理員類別
將下列類別範本複製到您的專案。
此類別繼承自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); } }
以您自己的名稱取代
SomeEventWeakEventManager
名稱。以您處理事件的對應名稱取代先前所述的三個名稱。 (
SomeEvent
、EventSource
和SomeEventEventArgs
)將弱式事件管理員類別的可見性(public/internal/private)設定為與所管理事件相同的可見性。
使用新的弱式事件管理員,而不是一般事件連結。
例如,如果您的程式代碼使用下列模式來訂閱事件:
source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
將其變更為下列模式:
SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
同樣地,如果您的程式代碼使用下列模式來取消訂閱事件:
source.SomeEvent -= new SomeEventEventHandler(OnSome);
將其變更為下列模式:
SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);