您是否需要在析构函数中删除事件处理程序?

时间:2011-07-12 10:49:24

标签: c# wpf events event-handling destructor

我使用了一些UserControls在运行期间在我的应用程序中创建和销毁(通过创建和关闭内部带有这些控件的子窗口)。
它是一个WPF UserControl,继承自System.Windows.Controls.UserControl。我无法覆盖Dispose()方法 PPMMSingleton,其生命周期与我的申请相同 现在在我的(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)这甚至有用吗?或者我是否必须存储新创建的ppmmEventHandler

我期待着你的回答。

9 个答案:

答案 0 :(得分:33)

由于PPMM是一个长期存在的对象(单例),因此这段代码没有多大意义。

这里的问题是,只要该事件处理程序引用该对象,它就没有资格进行垃圾收集,至少只要拥有该事件的其他对象是活动的。

因此,在析构函数中放置任何内容都是毫无意义的,因为:

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

    现在,当您实施Dispose时,可以说有关向IDisposable方法添加此类代码的不同论点。在 的情况下,它完全有意义,因为它的用户代码在预定义和受控制的点上调用Dispose

    然而,只有当对象符合垃圾收集条件且有终结器时才会调用终结器(析构函数),在这种情况下没有任何意义。

    至于问题nbr。 2,我将其视为“我能否取消订阅此类活动”,然后是,您可以。您需要保留您以前订阅的委托的唯一时间是在围绕匿名方法或lambda表达式构建委托时。当您围绕现有方法构建它时,它将起作用。


    修改:WPF。对,没看到那个标签。对不起,我的答案其余部分对WPF没有多大意义,因为我不是WPF大师,我真的不能说。但是,有一种方法可以解决这个问题。如果你可以改进它,那么在这里完全合法地挖掘另一个答案的内容。因此,如果有人知道如何使用WPF用户控件正确执行此操作,您可以自由解除我的答案的第一部分并添加相关的WPF部分。

    <击> 修改:让我回答这里的评论中的问题。

    由于所讨论的类是用户控件,因此其生命周期将与表单相关联。当表单关闭时,它将处理它拥有的所有子控件,换句话说,此处已存在Dispose方法

    如果用户控件管理自己的事件,那么用户控件处理它的正确方法是在Dispose方法中取消挂钩事件处理程序。

    (其余已删除)

答案 1 :(得分:8)

WPF不支持IDisposable。如果您正在实现需要清理的WPF控件,则应考虑改为挂钩LoadedUnloaded事件(或者另外)。

即。您连接到Loaded处理程序中的事件并断开Unloaded处理程序。当然,如果您的控件不需要在没有“加载”的情况下接收事件并且您可以正确支持许多加载/卸载周期,那么这只是一个选项。

使用Loaded / Unloaded事件的好处是,您无需在任何地方手动配置用户控件。但是,您应该知道应用程序关闭开始后不会触发Unloaded事件。例如。如果您的关机模式为OnMainWindowClose,则不会触发其他窗口的Unloaded事件。这通常不是问题。它只是意味着你无法在应用程序终止之前/期间必须发生的Unloaded中可靠地完成任务。

答案 2 :(得分:7)

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

其次,在我看来,如果此代码位于经常创建并且生命周期较短的对象内,则最好小心自行删除事件处理程序,因为这是一个链接持有者对象,阻止来自收集 GC

问候。

答案 3 :(得分:2)

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

那是因为只有在不再听任何事件的情况下它才会被销毁 如果它仍然在听事件,它就不会被摧毁。

答案 4 :(得分:2)

PPMM外部的东西是MyControl实例的生命周期更长吗?

如果是这样,除非PPMM_FactorChanged是静态方法,ppmmEventHandler将保持对实时MyControl实例的引用 - 这意味着实例永远不会有资格进行垃圾回收,终结者永远不会开枪。

您不需要保留ppmmEventHandler左右的删除代码。

答案 5 :(得分:2)

GC将负责这一点。虽然事件具有强引用,但它仅将其保存在父对象本身上。最后,只有MyControl会通过事件处理程序保留引用,因此GC将收集它。

另一方面使用终结者,这不是一个描述者。这是不好的做法。如果您要取消注册活动,则应考虑IDisposable

答案 6 :(得分:1)

事件处理程序很棘手,很容易隐藏资源泄漏。正如蒂格兰所说。使用IDisposeable并忘记析构函数。我建议您测量一下是否正确。只需在任务管理器中查看应用程序的内存消耗,就可以判断是否存在泄漏,如果通过加载和关闭几千个窗口来对其进行压力测试。

答案 7 :(得分:1)

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

不幸的是,只有订阅事件的对象保证它可以接受来自任何线程上下文的取消订阅请求,而不仅仅是订阅事件的上下文时,此模式才有效。 .NET要求作为“事件”合同的一部分,所有取消订阅方法都必须是线程安全的,不会对实现造成重大负担,但无论出于何种原因,MS都没有强加这样的要求。因此,即使Finalizer /析构函数发现事件应该尽快取消订阅,也没有标准的机制来实现这一点。

答案 8 :(得分:0)

2)这确实有效

1)我有一个案例(带有In-App消息传递服务),事件处理程序到未发布的全局对象,GC因此无法收集对象。我认为这通常是一种罕见的情况 - 如果您认为这种情况发生在你身上,那么使用像红色门的ANTS这样的分析器你可以轻松地进行内存分析。