弱事件模式

在应用程序中,附加到事件源的处理程序可能不会与将处理程序附加到源的侦听器对象一同销毁。 这种情况下会导致内存泄漏。 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 类型的参数传递给事件处理程序。

使用现有弱事件管理器类

  1. 查找现有弱事件管理器。

    有关 WPF 附带的弱事件管理器列表,请参阅 WeakEventManager 类中的继承层次结构。

  2. 使用新的弱事件管理器,而不是普通事件挂钩。

    例如,如果代码使用以下模式订阅事件:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    将其更改为以下模式:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    同样,如果代码使用以下模式取消订阅事件:

    source.SomeEvent -= new SomeEventEventHandler(OnSomeEvent);
    

    将其更改为以下模式:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

使用泛型弱事件管理器类

  1. 使用泛型 WeakEventManager<TEventSource,TEventArgs> 类,而不是普通事件挂钩。

    使用 WeakEventManager<TEventSource,TEventArgs> 注册事件侦听器时,需要将事件源和 EventArgs 类型作为类型参数提供给类并调用 AddHandler,如以下代码所示:

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

创建自定义弱事件管理器类

  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. 将弱事件管理器类的可见性(公共/内部/专用)设置为与其所管理的事件相同的可见性。

  5. 使用新的弱事件管理器,而不是普通事件挂钩。

    例如,如果代码使用以下模式订阅事件:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    将其更改为以下模式:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    同样,如果代码使用以下模式取消订阅事件:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    将其更改为以下模式:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

另请参阅