弱式事件模式 (WPF .NET)

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

重要

.NET 7 和 .NET 6 的桌面指南檔正在建置中。

必要條件

本文假設您已瞭解路由事件,而且您已閱讀 路由事件概觀。 若要遵循本文中的範例,如果您熟悉可延伸的應用程式標記語言(XAML),並知道如何撰寫 Windows Presentation Foundation (WPF) 應用程式,它很有説明。

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

接聽事件可能會導致記憶體流失。 接聽事件的一般技巧是使用語言特定的語法,將處理程式附加至來源上的事件。 例如,C# 語句 source.SomeEvent += new SomeEventHandler(MyEventHandler) 或 VB 語句 AddHandler source.SomeEvent, AddressOf MyEventHandler。 不過,這項技術會從事件來源到事件接聽程式建立強式參考。 除非明確取消註冊事件處理程式,否則接聽程序的物件存留期將受到來源物件存留期的影響。 在某些情況下,您可能會希望接聽程式的物件存留期受其他因素控制,例如它目前是否屬於應用程式的可視化樹狀結構。 每當來源的物件存留期超出接聽程式的有用物件存留期時,接聽程式會保持運作時間超過必要時間。 在此情況下,未配置的記憶體會等於記憶體流失。

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

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

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

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

如何實作弱式事件模式

有四種方式可以實作弱式事件模式,而每個方法都會使用不同的事件管理員。 選取最符合您案例的事件管理員。

  • 現有的弱式事件管理員

    當您想要訂閱的事件具有對應的 WeakEventManager時,請使用現有的弱式事件管理員類別。 如需 WPF 隨附的弱式事件管理員清單,請參閱 類別中的 WeakEventManager 繼承階層。 因為包含的弱式事件管理員有限,因此您可能需要選擇其中一個其他方法。

  • 一般弱式事件管理員

    當現有WeakEventManager項目無法使用時,請使用泛型WeakEventManager<TEventSource,TEventArgs>,而且您要尋找最簡單的方式來實作弱式事件。 不過,泛型 WeakEventManager<TEventSource,TEventArgs> 比現有或自定義弱式事件管理員效率低,因為它會使用反映從其名稱探索事件。 此外,使用泛型 WeakEventManager<TEventSource,TEventArgs> 註冊事件所需的程式代碼比使用現有或自定義 WeakEventManager更詳細。

  • 自訂弱式事件管理員

    當現有WeakEventManager項目無法使用且效率十分重要時,請建立自定義WeakEventManager。 雖然比泛型 WeakEventManager更有效率,但自定義 WeakEventManager 會要求您撰寫更多預先程序代碼。

  • 第三方弱式事件管理員

    當您需要其他方法未提供的功能時,請使用第三方弱式事件管理員。 NuGet 有一些 弱式事件管理員。 許多 WPF 架構也支援模式。

下列各節說明如何使用不同的事件管理員類型來實作弱式事件模式。 針對泛型和自定義弱式事件管理員範例,要訂閱的事件具有下列特性。

  • 事件名稱稱為 SomeEvent
  • 事件是由 SomeEventSource 類別引發。
  • 事件處理程式的類型為 EventHandler<SomeEventArgs>
  • 事件會將 類型的 SomeEventArgs 參數傳遞至事件處理程式。

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

  1. 尋找現有的弱式事件管理員。 如需 WPF 隨附的弱式事件管理員清單,請參閱 類別的 WeakEventManager 繼承階層。

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

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

    source.LostFocus += new RoutedEventHandler(Source_LostFocus);
    
    AddHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    將它變更為下列模式:

    LostFocusEventManager.AddHandler(source, Source_LostFocus);
    
    LostFocusEventManager.AddHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

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

    source.LostFocus -= new RoutedEventHandler(Source_LostFocus);
    
    RemoveHandler source.LostFocus, New RoutedEventHandler(AddressOf Source_LostFocus)
    

    將它變更為下列模式:

    LostFocusEventManager.RemoveHandler(source, Source_LostFocus);
    
    LostFocusEventManager.RemoveHandler(
        source, New EventHandler(Of RoutedEventArgs)(AddressOf Source_LostFocus))
    

使用泛型弱式事件管理員類別

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

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

WeakEventManager<SomeEventSource, SomeEventArgs>.AddHandler(source, "SomeEvent", Source_SomeEvent);
WeakEventManager(Of SomeEventSource, SomeEventArgs).AddHandler(
    source, "SomeEvent", New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))

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

  1. 將下列類別範本複製到您的專案。 下列類別繼承自 WeakEventManager 類別:

    class SomeEventWeakEventManager : WeakEventManager
    {
        private SomeEventWeakEventManager()
        {
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(SomeEventSource source,
                                      EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(handler));
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(SomeEventSource source,
                                         EventHandler<SomeEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (handler == null)
                throw new ArgumentNullException(nameof(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<SomeEventArgs>();
        }
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            SomeEventSource typedSource = (SomeEventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
    Class SomeEventWeakEventManager
        Inherits WeakEventManager
    
        Private Sub New()
        End Sub
    
        ''' <summary>
        ''' Add a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [AddHandler](source As SomeEventSource,
                                       handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedAddHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Remove a handler for the given source's event.
        ''' </summary>
        Public Shared Sub [RemoveHandler](source As SomeEventSource,
                                          handler As EventHandler(Of SomeEventArgs))
            If source Is Nothing Then Throw New ArgumentNullException(NameOf(source))
            If handler Is Nothing Then Throw New ArgumentNullException(NameOf(handler))
            CurrentManager.ProtectedRemoveHandler(source, handler)
        End Sub
    
        ''' <summary>
        ''' Get the event manager for the current thread.
        ''' </summary>
        Private Shared ReadOnly Property CurrentManager As SomeEventWeakEventManager
            Get
                Dim managerType As Type = GetType(SomeEventWeakEventManager)
                Dim manager As SomeEventWeakEventManager =
                    CType(GetCurrentManager(managerType), SomeEventWeakEventManager)
    
                If manager Is Nothing Then
                    manager = New SomeEventWeakEventManager()
                    SetCurrentManager(managerType, manager)
                End If
    
                Return manager
            End Get
        End Property
    
        ''' <summary>
        ''' Return a new list to hold listeners to the event.
        ''' </summary>
        Protected Overrides Function NewListenerList() As ListenerList
            Return New ListenerList(Of SomeEventArgs)()
        End Function
    
        ''' <summary>
        ''' Listen to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StartListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Stop listening to the given source for the event.
        ''' </summary>
        Protected Overrides Sub StopListening(source As Object)
            Dim typedSource As SomeEventSource = CType(source, SomeEventSource)
            AddHandler typedSource.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf OnSomeEvent)
        End Sub
    
        ''' <summary>
        ''' Event handler for the SomeEvent event.
        ''' </summary>
        Private Sub OnSomeEvent(sender As Object, e As SomeEventArgs)
            DeliverEvent(sender, e)
        End Sub
    End Class
    
  2. 將、SomeEventSomeEventSourceSomeEventArgs 重新命名SomeEventWeakEventManager為符合事件名稱。

  3. 設定弱式事件管理員類別的存取修飾詞,以符合所管理之事件的存取範圍。

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

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

    source.SomeEvent += new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    AddHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    將它變更為下列模式:

    SomeEventWeakEventManager.AddHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.AddHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

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

    source.SomeEvent -= new EventHandler<SomeEventArgs>(Source_SomeEvent);
    
    RemoveHandler source.SomeEvent, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent)
    

    將它變更為下列模式:

    SomeEventWeakEventManager.RemoveHandler(source, Source_SomeEvent);
    
    SomeEventWeakEventManager.RemoveHandler(
        source, New EventHandler(Of SomeEventArgs)(AddressOf Source_SomeEvent))
    

另請參閱