弱式事件模式Weak Event Patterns

在應用程式中,附加到事件來源的處理常式可能不會在與附加處理常式至來源的接聽程式物件協調時遭到破壞。In applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. 這種情況可能會導致記憶體流失。This situation can lead to memory leaks. Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) 引進了一個設計模式,可以用來解決這個問題,方法是提供特定事件的專用管理員類別,並在該事件的接聽程式上執行介面。introduces a design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. 這個設計模式就是所謂的「弱式事件模式」。This design pattern is known as the weak event pattern.

為什麼要執行弱式事件模式?Why Implement the Weak Event Pattern?

接聽事件可能會導致記憶體流失。Listening for events can lead to memory leaks. 接聽事件的一般方法是使用特定語言的語法,將處理常式附加至來源上的事件。The typical technique for listening to an event is to use the language-specific syntax that attaches a handler to an event on a source. 例如,在中C#,該語法為: source.SomeEvent += new SomeEventHandler(MyEventHandler)For example, in C#, that syntax is: source.SomeEvent += new SomeEventHandler(MyEventHandler).

這項技術會建立從事件來源到事件接聽程式的強式參考。This technique creates a strong reference from the event source to the event listener. 通常,附加接聽項的事件處理常式會導致接聽程式具有受來源物件存留期影響的物件存留期(除非明確地移除事件處理常式)。Ordinarily, attaching an event handler for a listener causes the listener to have an object lifetime that is influenced by the object lifetime of the source (unless the event handler is explicitly removed). 但在某些情況下,您可能想要讓接聽程式的物件存留期受到其他因素的控制,例如它目前是否屬於應用程式的視覺化樹狀結構,而不是來源的存留期。But in certain circumstances, you might want the object lifetime of the listener to be controlled by other factors, such as whether it currently belongs to the visual tree of the application, and not by the lifetime of the source. 每當來源物件存留期延伸超出接聽程式的物件存留期時,一般事件模式就會導致記憶體流失:接聽程式的存留時間比預期的長。Whenever the source object lifetime extends beyond the object lifetime of the listener, the normal event pattern leads to a memory leak: the listener is kept alive longer than intended.

弱式事件模式的設計目的是要解決這個記憶體流失的問題。The weak event pattern is designed to solve this memory leak problem. 每當接聽程式需要註冊事件時,就可以使用弱式事件模式,但是接聽程式並不明確知道何時取消註冊。The weak event pattern can be used whenever a listener needs to register for an event, but the listener does not explicitly know when to unregister. 只要來源的物件存留期超過接聽程式的有用物件存留期,也可以使用弱式事件模式。The weak event pattern can also be used whenever the object lifetime of the source exceeds the useful object lifetime of the listener. (在此案例中,有用的是由您所決定)。弱式事件模式可讓接聽程式註冊並接收事件,而不會以任何方式影響接聽項的物件存留期特性。(In this case, useful is determined by you.) The weak event pattern allows the listener to register for and receive the event without affecting the object lifetime characteristics of the listener in any way. 實際上,來自來源的隱含參考不會判斷接聽程式是否符合垃圾收集的資格。In effect, the implied reference from the source does not determine whether the listener is eligible for garbage collection. 參考是弱式參考,因此會命名弱式事件模式和相關的 Api。The reference is a weak reference, thus the naming of the weak event pattern and the related APIs. 接聽程式可以進行垃圾收集或以其他方式終結,而來源可以繼續,而不會保留目前已損毀物件的構成處理常式參考。The listener can be garbage collected or otherwise destroyed, and the source can continue without retaining noncollectible handler references to a now destroyed object.

誰應該實行弱式事件模式?Who Should Implement the Weak Event Pattern?

執行弱式事件模式主要是對控制項作者感興趣。Implementing the weak event pattern is interesting primarily for control authors. 身為控制項作者,您主要負責控制控制項的行為和內含專案,以及它對插入它的應用程式所造成的影響。As a control author, you are largely responsible for the behavior and containment of your control and the impact it has on applications in which it is inserted. 這包括控制物件存留期行為,特別是處理所述記憶體流失的問題。This includes the control object lifetime behavior, in particular the handling of the described memory leak problem.

某些案例原本就是將自己帶到弱式事件模式的應用程式。Certain scenarios inherently lend themselves to the application of the weak event pattern. 其中一種情況就是資料系結。One such scenario is data binding. 在 [資料系結] 中,來源物件與接聽程式物件完全獨立,這是系結的目標。In data binding, it is common for the source object to be completely independent of the listener object, which is a target of a binding. WPFWPF 資料系結的許多層面已經有在事件的執行方式中所套用的弱式事件模式。Many aspects of WPFWPF data binding already have the weak event pattern applied in how the events are implemented.

如何執行弱式事件模式How to Implement the Weak Event Pattern

有三種方式可執行弱式事件模式。There are three ways to implement weak event pattern. 下表列出這三種方法,並提供一些使用時機的指引。The following table lists the three approaches and provides some guidance for when you should use each.

方法Approach 執行時機When to Implement
使用現有的弱式事件管理員類別Use an existing weak event manager class 如果您想要訂閱的事件具有對應的 WeakEventManager,請使用現有的弱式事件管理員。If the event you want to subscribe to has a corresponding WeakEventManager, use the existing weak event manager. 如需 WPF 隨附之弱式事件管理員的清單,請參閱 WeakEventManager 類別中的繼承階層架構。For a list of weak event managers that are included with WPF, see the inheritance hierarchy in the WeakEventManager class. 由於內含的弱式事件管理員受到限制,因此您可能需要選擇其中一種方法。Because the included weak event managers are limited, you will probably need to choose one of the other approaches.
使用一般弱式事件管理員類別Use a generic weak event manager class 當現有 WeakEventManager 無法使用、您想要執行簡單的方法,而且不在意效率時,請使用一般 WeakEventManager<TEventSource,TEventArgs>Use a generic WeakEventManager<TEventSource,TEventArgs> when an existing WeakEventManager is not available, you want an easy way to implement, and you are not concerned about efficiency. 一般 WeakEventManager<TEventSource,TEventArgs> 比現有或自訂的弱式事件管理員更有效率。The generic WeakEventManager<TEventSource,TEventArgs> is less efficient than an existing or custom weak event manager. 例如,泛型類別會執行更多反映,以便在指定事件名稱的情況下探索事件。For example, the generic class does more reflection to discover the event given the event's name. 此外,使用泛型 WeakEventManager<TEventSource,TEventArgs> 來註冊事件的程式碼,比使用現有或自訂的 WeakEventManager更詳細。Also, the code to register the event by using the generic WeakEventManager<TEventSource,TEventArgs> is more verbose than using an existing or custom WeakEventManager.
建立自訂弱式事件管理員類別Create a custom weak event manager class 當現有 WeakEventManager 無法使用,而您想要獲得最佳效率時,請建立自訂 WeakEventManagerCreate a custom WeakEventManager when an existing WeakEventManager is not available and you want the best efficiency. 使用自訂 WeakEventManager 來訂閱事件將會更有效率,但您在一開始就會產生撰寫更多程式碼的成本。Using a custom WeakEventManager to subscribe to an event will be more efficient, but you do incur the cost of writing more code at the beginning.
使用協力廠商弱式事件管理員Use a third-party weak event manager NuGet 有數個弱式事件管理員,而許多 WPF 架構也支援模式(例如,請參閱Prism 的檔,以取得鬆散結合的事件訂用帳戶)。NuGet has several weak event managers and many WPF frameworks also support the pattern (for instance, see Prism's documentation on loosely coupled event subscription).

下列各節說明如何執行弱式事件模式。The following sections describe how to implement the weak event pattern. 基於此討論的目的,訂閱的事件具有下列特性。For purposes of this discussion, the event to subscribe to has the following characteristics.

  • 事件名稱為 SomeEventThe event name is SomeEvent.

  • 事件是由 EventSource 類別引發。The event is raised by the EventSource class.

  • 事件處理常式的類型為: SomeEventEventHandler (或 EventHandler<SomeEventEventArgs>)。The event handler has type: SomeEventEventHandler (or EventHandler<SomeEventEventArgs>).

  • 事件會將類型 SomeEventEventArgs 的參數傳遞給事件處理常式。The event passes a parameter of type SomeEventEventArgs to the event handlers.

使用現有的弱式事件管理員類別Using an Existing Weak Event Manager Class

  1. 尋找現有的弱式事件管理員。Find an existing weak event manager.

    如需 WPF 隨附之弱式事件管理員的清單,請參閱 WeakEventManager 類別中的繼承階層架構。For a list of weak event managers that are included with WPF, see the inheritance hierarchy in the WeakEventManager class.

  2. 使用新的弱式事件管理員,而不是一般事件連結。Use the new weak event manager instead of the normal event hookup.

    例如,如果您的程式碼使用下列模式來訂閱事件:For example, if your code uses the following pattern to subscribe to an event:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);  
    

    將它變更為下列模式:Change it to the following pattern:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);  
    

    同樣地,如果您的程式碼使用下列模式來取消訂閱事件:Similarly, if your code uses the following pattern to unsubscribe from an event:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);  
    

    將它變更為下列模式:Change it to the following pattern:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);  
    

使用一般弱式事件管理員類別Using the Generic Weak Event Manager Class

  1. 使用泛型 WeakEventManager<TEventSource,TEventArgs> 類別,而不是一般事件連結。Use the generic WeakEventManager<TEventSource,TEventArgs> class instead of the normal event hookup.

    當您使用 WeakEventManager<TEventSource,TEventArgs> 來註冊事件接聽程式時,您會提供事件來源和 EventArgs 類型做為類別的型別參數,並呼叫 AddHandler,如下列程式碼所示:When you use WeakEventManager<TEventSource,TEventArgs> to register event listeners, you supply the event source and EventArgs type as the type parameters to the class and call AddHandler as shown in the following code:

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

建立自訂弱式事件管理員類別Creating a Custom Weak Event Manager Class

  1. 將下列類別範本複製到您的專案。Copy the following class template to your project.

    這個類別繼承自 WeakEventManager 類別。This class inherits from the WeakEventManager class.

    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 名稱取代為您自己的名稱。Replace the SomeEventWeakEventManager name with your own name.

  3. 將先前所述的三個名稱取代為您事件的對應名稱。Replace the three names described previously with the corresponding names for your event. SomeEventEventSourceSomeEventEventArgs(SomeEvent, EventSource, and SomeEventEventArgs)

  4. 將弱式事件管理員類別的可見度(公用/內部/私用)設定為與它所管理之事件相同的可見度。Set the visibility (public / internal / private) of the weak event manager class to the same visibility as event it manages.

  5. 使用新的弱式事件管理員,而不是一般事件連結。Use the new weak event manager instead of the normal event hookup.

    例如,如果您的程式碼使用下列模式來訂閱事件:For example, if your code uses the following pattern to subscribe to an event:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);  
    

    將它變更為下列模式:Change it to the following pattern:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);  
    

    同樣地,如果您的程式碼使用下列模式來取消訂閱事件:Similarly, if your code uses the following pattern to unsubscribe to an event:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);  
    

    將它變更為下列模式:Change it to the following pattern:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);  
    

請參閱See also