65.9K
CodeProject 正在变化。 阅读更多。
Home

EventSubscriptionManager - 不再泄漏事件委托

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (4投票s)

2010 年 3 月 7 日

CPOL

4分钟阅读

viewsIcon

33935

downloadIcon

164

一个管理已附加事件的解决方案

引言

当您手动将代码中的事件处理程序“订阅”到由控件或任何发布事件的通用类发布的事件时,您就隐式地承担了取消订阅事件的责任。如果您未能这样做,您将为每一个未能取消订阅的事件泄露一个事件委托。

通常,您会在类的构造函数中“订阅”事件,然后在类的析构函数/终结器/Dispose 方法中“取消订阅”。当然,这并不是什么难事……只要您不忘记的话。

此外,如果您的代码中到处都附加了事件,而不仅仅是在构造函数/析构函数中,那会怎么样?即使您记得进行清理,这也可能是一件棘手的事情。

我编写了附加类 EventSubscriptionManager 来解决这个问题。

背景

这里的想法是简化事件订阅的管理。该解决方案包含两个简单的类:

  • EventSubscriptionManager,它主要是一个 EventSubscription 对象的集合。
  • EventSubscription,它管理对给定源对象及其事件的单个订阅。

这些类负责订阅事件,然后在您忘记或者压根就不想处理事件取消订阅的情况下,负责取消订阅。

当我决定厌倦了事件订阅管理并下定决心要解决它时,我考虑了几项要求。

  1. 它必须易于使用。(否则,有什么意义呢?)
  2. 它必须允许我随时订阅和取消订阅。
  3. 它必须为任何给定的事件维护多个订阅。
  4. 它必须支持所有事件委托,即使是那些不继承自 EventHandler 的。
  5. 它必须负责删除所有事件订阅,无论我何时何地在代码中分配它们。

正常的订阅通常如下所示:

 // subscribing to get a method in your class, called MyEventMethod
 someObject.SomeEvent += new SomeEventHandler(MyEventMethod); 

// or the shorthand way: someObject.SomeEvent += new MyEventMethod;

同样……取消订阅也类似:

// time to unsubscribe to events....
someObject.SomeEvent -= new SomeEventHandler(MyEventMethod);

如您所见,这很简单。事件与委托 101。但是,要做到这一点……嗯……正如我上面所说,那是另一回事了。

Using the Code

使用该代码非常方便。

首先,下载附加的源文件EventSubscriptionManager.cs 并将其添加到您的项目中,或者将其添加到您的“实用工具库”中以供重用。

第二,添加对该类命名空间的引用:

using EventSubscription.Manager;  

第三,在您想要管理资源的 Form Class 中添加该类的一个实例成员:

EventSubscriptionManager  m_eventMgr = new EventSubscriptionManager(); 

现在您已准备就绪。使用上面的示例,我们将订阅相同的事件:

// subscribing to get a function called MyEventMethod
m_eventMgr.Subscribe(someObject, "SomeEvent", new SomeEventHandler(MyEventMethod)); 

同样……取消订阅也类似:

// time to unsubscribe to events....
m_eventMgr.Unsubscribe(someObject, "SomeEvent", new SomeEventHandler(MyEventMethod)); 

如果您未能取消订阅任何事件,当 m_eventMgr 被销毁时,这会被处理。事实上,根本没有必要考虑管理事件订阅的销毁。这是管理器的职责。

如果您确实希望“删除”所有事件,可能用于重置或重新初始化事件,您可以简单地调用:

m_eventMgr.UnsubscribeAll(); // reset our subscriptions...

就是这样!不再有泄露。不再需要费力跟踪您订阅了什么。

它是如何工作的?

反射来帮忙!

所有事件处理程序都派生自 MulticastDelegate。所以,这是所有事件处理程序的基类,无论它们是什么。因此,它适用于所有事件和所有事件处理程序。

使用反射,它可以获取指定事件的 EventInfo ,该 EventInfo 包含此对象特定事件的“调用列表”。此列表可能包含对其他事件处理程序的引用。我们当然只对我们自己的感兴趣。

EventInfo eventInfo = eventSource.GetType().GetEvent(m_eventName, c_flags);  

从这个类中,我们可以通过以下方式将我们的事件处理程序添加到源事件:

eventInfo.AddEventHandler(eventSource, m_eventHandler);    

这会将我们的事件处理程序添加到调用列表中,以及在触发事件时已注册的其他委托。

同样,我们可以通过以下方式调用事件处理程序的取消订阅:

eventInfo.RemoveEventHandler(eventSource, m_eventHandler);   

这将从对象事件调用列表中删除我们的委托(至少是第一个实例,如果您有多个实例)。

请注意,示例中未使用 m_eventSource 。这是因为 m_eventSource 已更改为 WeakReference ,以处理源对象可能希望在我们取消订阅之前被垃圾回收的情况。当我们在取消订阅时,eventSource 现在代表我们从 WeakReference m_eventSource 获取的强引用,前提是源对象尚未被垃圾回收。

就是这样。希望您觉得它有用!

历史

已将源对象更改为使用“WeakReference”。

© . All rights reserved.