匿名事件处理程序和释放

本文关键字:释放 程序 事件处理 | 更新日期: 2023-09-27 18:26:19

我有一个关于匿名事件处理程序的相当简短的问题:

这是我的代码

public void AddTestControl(Control ctrl)
{
    ctrl.Disposed += (o, e) => { RemoveTestControl(ctrl); };
    ctrl.SomeEvent += _Control_SomeEvent;
}
public void RemoveTestControl(Control ctrl)
{
    ctrl.SomeEvent -= _Control_SomeEvent;
}

上面的这段代码是否正常,还是应该重写代码以删除已释放的事件处理程序?像这样:

public void AddTestControl(Control ctrl)
{
    ctrl.Disposed += _Control_Disposed;
    ctrl.SomeEvent += _Control_SomeEvent;
}
public void RemoveTestControl(Control ctrl)
{
    ctrl.Disposed -= _Control_Disposed;
    ctrl.SomeEvent -= _Control_SomeEvent;
}

匿名事件处理程序和释放

通常,需要从对象中删除事件处理程序以使其符合垃圾回收条件的唯一情况是发布对象(定义事件的对象(的生存时间比订阅者对象(包含事件处理程序的对象(生存时间长。在这种情况下,GC 在超出范围时将无法释放订阅者,因为它仍被发布者引用。

在这种情况下,假设这是 WebForms 或 WinForms,发布者(即Control对象(很可能是订阅者的子对象(可能是PageForm(,这将是第一个超出范围的对象,随身携带其所有相关对象。因此,无需删除事件处理程序

对我来说,

取消订阅事件总是感觉更干净,即使在我知道订阅者总是比发布者(引发事件的对象(活的时间的情况下也是如此:事件将永远不会再次引发,发布者不再可访问并且可以收集。

再说一次,有多少人会费心去取消订阅每个事件处理程序,例如WinForms应用程序?对象引用指向从发布者到订阅者,而不是相反,因此可以在订阅者生存时收集发布者。它不会带来与相反情况相同的危险,在相反的情况下,长期存在的发布者(例如静态事件(可能会浪费地让潜在的大型订阅者在收集它们之后很长时间内存活。

如果要/需要取消订阅,则取消订阅同一委托的要求会使匿名事件处理程序有点痛苦。反应式扩展以一种巧妙的方式解决了这个问题:订阅返回一个IDisposable,该在释放时取消订阅,而不必记住您订阅的委托。将所有订阅放入一个CompositeDisposable只需一次Dispose调用即可取消订阅所有内容。

两个codez都很好,但我喜欢第二个作为个人喜好的问题。它读起来比第一个更清晰。

在第一个代码之上,有匿名 lambda 委托,它获得对 ctrl 的当前引用。该代码可能会意外运行,具体取决于情况和编译优化设置:无论调用是否内联。

更不用说代码存在体系结构问题:你有 ControlOwner 和一堆子控件。我认为您在运行时将子控件添加到 ControlOwner,然后尝试通过将 ControlOwner 订阅到子控件事件来对其行为做出反应。这对于_ButtonClicked等事件很好。但对处置不利。让子控件自行处理它,所有者控件不需要知道它。更何况它当时可能还不存在[n]。调用"处置"。

总之: * 最好将处置事件单独保留在 ChildControl 上,并在 ChildControl 中执行所有清理工作。 * 无需取消订阅活动。事件调度程序将检查订阅者是否处于活动状态。