5

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

这是我拥有的代码

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

public void RemoveTestControl(Control ctrl)
{
    ctrl.SomeEvent -= _Control_SomeEvent;
}

上面的代码是否正常,或者是否应该重写代码以删除 Disposed Event Handler?像这样的东西:

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;
}
4

3 回答 3

8

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

在这种情况下,假设这是 WebForms 或 WinForms,发布者(即Control对象)很可能是订阅者的子对象(可能是 aPage或 a Form),这将是第一个超出范围的对象与它的对象。因此无需删除事件处理程序

于 2012-03-02T10:11:43.267 回答
2

取消订阅事件对我来说总是感觉更干净,即使在我知道订阅者总是比发布者(引发事件的对象)更长寿的情况下:事件永远不会再次引发,发布者不再可访问并且可以被收集。

话又说回来,有多少人会为取消订阅例如 WinForms 应用程序中的每个事件处理程序而烦恼?对象引用从发布者指向订阅者,而不是相反,因此可以在订阅者存在时收集发布者。它不会带来与相反情况相同的危险,在这种情况下,长期存在的发布者(例如静态事件)可能会浪费地让潜在的大型订阅者在可能被收集后很长时间仍然活着。

如果您想要/需要取消订阅,那么取消订阅同一个委托的要求会使匿名事件处理程序有点痛苦。反应式扩展以一种简洁的方式解决了这个问题:订阅时不必记住您订阅的委托,而是返回一个取消订阅的IDisposable对象。将您的所有订阅都放入一个CompositeDisposable,只需一个Dispose电话即可取消订阅所有内容。

于 2012-03-02T10:38:27.810 回答
1

两个codez都很好,但我个人喜欢第二个。它比第一个更清晰。

除了第一个代码之外,还有匿名 lambda 委托,它获取对 ctrl 的当前引用。该代码可能会根据情况和编译优化设置出现意外行为:调用是否内联。

更不用说代码存在架构问题:您有 ControlOwner 和一堆子控件。我认为您在运行时将子控件添加到 ControlOwner,然后尝试通过将 ControlOwner 订阅到 childControl 事件来对它们的行为做出反应。这对于 _ButtonClicked 等事件很好。但对 Dispose 不利。让子控件自己处理它, OwnerControl 不需要知道它。更不用说在调用 ChildControl[n].Dispose 时它可能不存在。

简而言之: * 最好将 dispose 事件单独留在 ChildControl 上,并在 ChildControl.Dispose 中进行所有清理 * 没有必要取消订阅事件。事件调度程序将检查订阅者是否还活着。

于 2012-03-02T10:45:52.377 回答