弱事件模式 (WPF .NET)
在应用程序中,附加到事件源的处理程序可能不会与将处理程序附加到源的侦听器对象一同销毁。 这种情况可能会导致内存泄漏。 Windows Presentation Foundation (WPF)引入了可用于解决此问题的设计模式。 设计模式为特定事件提供专用的管理器类,并在该事件的侦听器上实现接口。 此设计模式称为 弱事件模式。
先决条件
本文假定你已具备路由事件的基本知识,并且已阅读 路由事件概述。 若要遵循本文中的示例,如果你熟悉可扩展应用程序标记语言(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
的参数传递给事件处理程序。
使用现有弱事件管理器类
查找现有弱事件管理器。 有关 WPF 附带的弱事件管理器列表,请参阅 WeakEventManager 类的继承层次结构。
使用新的弱事件管理器来代替正常的事件绑定。
例如,如果代码使用以下模式订阅事件:
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))
创建自定义弱事件管理器类
将以下类模板复制到项目。 以下类继承自 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
重命名
SomeEventWeakEventManager
、SomeEvent
、SomeEventSource
和SomeEventArgs
以匹配事件名称。为弱事件管理器类设置 访问修饰符,以匹配它管理的事件的可访问性。
使用新的弱事件管理器,而不是普通事件挂钩。
例如,如果代码使用以下模式订阅事件:
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))