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

本文关键字:删除 事件处理 程序 析构函数 是否 | 更新日期: 2023-09-27 18:01:54

我使用一些UserControls在运行时在我的应用程序中创建和销毁(通过创建和关闭带有这些控件的子窗口)。
它是一个WPF UserControl,继承自System.Windows.Controls.UserControl。没有我可以重写的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)这是否有效?或者我必须存储新创建的ppmmEventHandler吗?

我期待着你的答案。

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

由于PPMM是长寿命对象(单例),因此这段代码没有多大意义。

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

因此,在析构函数中放入任何东西都是没有意义的,如:

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

总之,不要这样做

现在,当您实现IDisposable时,可以使用不同的参数来向Dispose方法添加这样的代码。在的情况下,它完全有意义,因为它的用户代码在预定义的控制点上调用Dispose

但是,只有当对象有资格进行垃圾收集并且具有终结器时,才调用终结器(析构函数),在这种情况下,没有意义。

关于第2题。2,我认为这是"我可以取消订阅这样的事件吗?",那么是的,你可以。只有在围绕匿名方法或lambda表达式构造委托时,才需要保留用于订阅的委托。当你在一个已经存在的方法上构造它的时候,它会工作的。


编辑: WPF。对,没看到那个标签。对不起,我的回答的其余部分对WPF没有多大意义,因为我不是WPF专家,我真的不能说。然而,有一种方法可以解决这个问题。在SO上,如果你能改进另一个答案,窃取它的内容是完全合法的。因此,如果有人知道如何正确地使用WPF用户控件做到这一点,您可以自由地取消我的答案的整个第一部分,并添加WPF的相关部分。

<罢工> 编辑:让我也在这里的评论中回答这个问题。

由于所讨论的类是一个用户控件,它的生命周期将与表单绑定。当窗体关闭时,它将释放它拥有的所有子控件,换句话说,这里已经有一个dispose方法。

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

(rest)删除

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

其次,在我看来,如果这段代码位于一个经常创建且生命周期较短的对象中,最好是注意自己删除事件处理程序,因为这是指向holder对象的链接,这将阻止GC收集

致意。

WPF不支持IDisposable。如果你正在实现一个需要清理的WPF控件,你应该考虑挂钩到LoadedUnloaded事件,而不是(或另外)。

。在Loaded处理程序中连接到事件,并在Unloaded处理程序中断开连接。当然,这只是一个选项,如果你的控件不需要接收事件,而它没有"加载",如果你可以正确地支持多个加载/卸载周期。

使用Loaded/Unloaded事件的优点是您不必在任何使用它的地方手动处置用户控件。但是,您应该注意,在应用程序开始关闭之后,Unloaded事件不会被触发。例如,如果你的关闭模式是OnMainWindowClose,其他窗口的Unloaded事件将不会被触发。但这通常不是问题。这只是意味着你不能在Unloaded中可靠地做一些必须在应用程序终止之前/时发生的事情。

如果代码到达了析构函数,那就无关紧要了。

这是因为它只有在不再监听任何事件时才会被销毁。
如果它还在监听事件,就不会被摧毁了。

PPMM是否具有比MyControl实例更长的生命周期?

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

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

GC会处理这个问题。虽然事件持有一个强引用,但它只在父对象本身持有它。最后,只有MyControl将通过事件处理程序保留引用,因此GC将收集它。

,另一方面,使用终结器,它不是一个描述符。是为了这个坏习惯。如果您想注销一个事件,您应该考虑IDisposable .

事件处理程序很棘手,可以很容易地隐藏资源泄漏。正如Tigran所说。使用idisposable,忘记析构函数。我建议你衡量一下自己是否做对了。只要在任务管理器中查看你的应用程序的内存消耗,如果你通过加载和关闭几千个窗口来进行压力测试,就会知道是否存在泄漏。

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

不幸的是,只有当订阅事件的对象保证它可以接受来自任何线程上下文的退订请求,而不仅仅是订阅事件的上下文时,这种模式才能工作。如果. net要求所有退订方法都必须是线程安全的,作为"事件"契约的一部分,这不会给实现带来很大的负担,但无论出于何种原因,MS并没有强加这样的要求。因此,即使终结器/析构器发现应该尽快取消订阅事件,也没有标准机制可以实现这一点。

2)这工作

1)我有一个案例(与应用内消息传递服务),事件处理程序到一个全局对象,没有释放,GC无法收集对象,因为。我认为这通常是一种罕见的情况-使用像red gate的ANTS这样的分析器,如果您认为这种情况发生在您身上,您可以轻松地进行内存分析。