你需要在析构函数中移除一个事件处理程序吗?

我使用了一些UserControls ,它们在运行时在应用程序中创build和销毁(通过创build和closures内部具有这些控件的子窗口)。
这是一个WPF用户控件,并从System.Windows.Controls.UserControlinheritance。 没有可以覆盖的Dispose()方法。
PPMM是与我的应用程序相同的生命周期的Singleton
现在在我的(WPF) UserControl的构造函数中,我添加一个事件处理程序:

 public MyControl() { InitializeComponent(); // hook up to an event PPMM.FactorChanged += new ppmmEventHandler(PPMM_FactorChanged); } 

我习惯于在析构函数中删除这样的事件处理程序:

 ~MyControl() { // hook off of the event PPMM.FactorChanged -= new ppmmEventHandler(PPMM_FactorChanged); } 

今天我偶然发现,并想知道:

1)这是必要的吗? 还是GC照顾它?

2)这是否工作? 或者我将不得不存储新创build的ppmmEventHandler

我期待着你的回答。

由于PPMM是一个长寿命的对象(单例),所以这段代码没有什么意义。

这里的问题是,只要该事件处理程序引用该对象, 就不会有资格进行垃圾回收 ,至less只要拥有该事件的其他对象处于活动状态即可。

因此,将任何东西放在析构函数中是没有意义的,如下所示:

  1. 事件处理程序已被删除,因此该对象有资格进行垃圾回收
  2. 事件处理程序不会被删除,拥有的对象不符合垃圾回收的条件,因此终结者将永远不会被调用
  3. 两个对象都有资格进行垃圾回收,在这种情况下,您不应该在终结器中访问其他对象,因为您不知道它的内部状态

总之, 不要这样做

现在,当你实现IDisposable时候,可以将这样的代码添加到Dispose方法中。 在这种情况下,由于它的用户代码正在调用Dispose ,在一个预定义的控制点上,这是完全有意义的。

但是,终结器(析构函数)只有在对象符合垃圾回收的条件时才会调用,并且有一个终结器,在这种情况下没有意义。

至于问题 2,我认为“我可以不喜欢这样的事件”,那么是的,你可以。 你只需要坚持你用来订阅的委托就是当你围绕匿名方法或lambdaexpression式构造委托时。 当你正在构build一个现有的方法,它将工作。


编辑 :WPF。 对,没有看到那个标签。 对不起,我的答案的其余部分对WPF没什么意义,因为我不是WPF大师,所以我不能说。 但是,有一种方法可以解决这个问题。 如果你能改善这个问题的话,那么在另一个答案的内容上挖掘这个内容是完全合法的。 所以,如果有人知道如何正确使用WPF用户控件做到这一点,你可以自由地解除我的答案的整个第一部分,并添加WPF的相关位。

编辑 :让我回应这里的评论中的问题。

由于所讨论的类是一个用户控制,它的生命周期将被绑定到一个表单上。 当表单closures时,它将处理它拥有的所有子控件,换句话说, 这里已经有一个Dispose方法

如果用户控件pipe理自己的事件,则用户控件正确处理此事件的方法是在Dispose方法中解除事件处理程序。

(rest删除)

首先我会说不要使用析构函数,而是使用Dispose()来清除资源。

其次,如果这个代码位于一个经常创build的对象中,并且生命周期较短,那么最好事先删除事件处理程序,因为这是一个持有者对象的链接,这将阻止 GC 收集它

问候。

WPF不太支持IDisposable 。 如果您正在实现需要清理的WPF控件,则应考虑连接(或另外)连接到LoadedUnloaded事件。

即您连接到Loaded处理程序中的事件,并在Unloaded处理程序中断开连接。 当然,这只是一个选项,如果您的控件在“未加载”时不需要接收事件,并且您可以正确地支持许多加载/卸载循环。

使用Loaded / Unloaded事件的好处在于,您无需手动将用户控件置于使用的任何位置。 但是,您应该意识到在应用程序closures开始之后Unloaded事件不会被触发。 例如,如果您的closures模式是OnMainWindowClose ,其他窗口的Unloaded事件将不会被触发。 这通常不是一个问题。 这只是意味着你不能在Unloaded之前可靠地执行一些必须在应用程序终止之前/之前发生的事情。

如果代码到达析构函数,那么它就不重要了。

那是因为如果它不再听任何事情,它就会被摧毁。
如果还在听事件的话,它不会被摧毁。

PPMM一些外部的MyControl实例有更长的生命周期吗?

如果是这样,除非PPMM_FactorChanged是一个静态方法, ppmmEventHandler将保持对MyControl实例的引用 – 这意味着该实例永远不会有资格进行垃圾回收,并且终结器将永远不会触发。

您不需要为移除代码保留ppmmEventHandler

GC会照顾到这一点。 虽然这个事件有一个强有力的参考,但它只是在父对象本身上。 最后只有MyControl会通过事件处理器保持一个引用,因此GC将收集它。

另一方面使用终结者,这不是一个descrutor。 这是不好的做法。 如果你想取消注册一个事件,你应该考虑IDisposable

事件处理程序是棘手的,可以很容易地隐藏资源泄漏。 正如Tigran所说。 使用IDisposeable并忘记析构函数。 我build议测量你是否正确。 只要在任务pipe理器中查看应用程序的内存消耗情况,就可以知道是否存在泄漏,如果您通过加载和closures几千个窗口来强调testing一下就会发现泄漏。

如果事件发布者确保取消订阅是线程安全的则在某些情况下,取消订阅Finalizer /析构函数中的事件可能会有用。 对于取消订阅自己的事件的对象将是无用的,但作为一个可行的模式可以有一个面向公众的对象持有一个私人对象的引用,实际上“做所有的工作”,并有私人对象订阅事件。 如果没有私人对象的引用返回到公共对象,那么一旦没有人对它有兴趣,公共对象就有资格进入最终定案。 它的终结器将能够代表私人对象取消订阅。

不幸的是,只有事件订阅的对象保证它可以接受来自任何线程上下文的取消订阅请求,而不仅仅是事件订阅的上下文,这种模式才能工作。 .NET要求作为“事件”合约的一部分,所有取消订阅方法必须是线程安全的,不会给实现带来很大的负担,但MS不会强加这样的要求。 因此,即使Finalizer /析构者发现一个事件应该尽快取消订阅,也没有一个标准的机制可以做到这一点。

2)这是行不通的

1)我有一个案件(与应用程序内消息传递服务)事件处理程序的全球对象,因为没有释放和GC无法收集的对象。 我认为这通常是一种罕见的情况 – 使用类似红色门ant的分析器,如果您认为这发生在您身上,您可以轻松地进行内存分析。