我对匿名事件处理程序有一个相当简短的问题:
这是我的代码:
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;
}
答案 0 :(得分:8)
通常,您需要从对象中删除事件处理程序才能使其符合垃圾回收的唯一情况是 publisher 对象(定义事件的对象)的寿命更长而不是订户对象(包含事件处理程序的对象)。在这种情况下,当用户超出范围时,GC将无法释放用户,因为它仍然被出版商引用。
在这种情况下,假设这是WebForms或WinForms,发布者(即Control
对象)很可能是订阅者的子对象(可能是{{1}或者Page
),这将是第一个超出范围的所有关联对象。因此,无需删除事件处理程序。
答案 1 :(得分:2)
即使在我知道订阅者总是比发布者(对象)更长的情况下,取消订阅活动总是感觉更清洁提升事件):事件永远不会再次被提起,发布者不再可以访问并且可以被收集。
然后再次,有多少人去取消订阅每个事件处理程序的麻烦,例如一个WinForms应用程序?对象引用从发布者到订阅者,而不是相反,因此可以在订阅者生成时收集发布者。它没有与相反情况相同的危险,在这种情况下,长期存在的发布者(例如静态事件)可能浪费地将潜在的大订户保留在收集后很长时间。
如果您想要/需要取消订阅,那么取消订阅相同委托的要求会使匿名事件处理程序变得有点痛苦。 Reactive Extensions以一种巧妙的方式解决了这个问题:订阅返回IDisposable
,而不是必须记住您订阅的委托,在处理时取消订阅。将您的所有订阅放入CompositeDisposable
,只需拨打一次Dispose
即可取消订阅。
答案 2 :(得分:1)
两个codez都很好,但我喜欢第二个作为个人喜好的问题。它比第一个更清晰。
在第一个代码的顶部,有一个匿名的lambda委托,它获得了对ctrl的当前引用。根据情况和编译优化设置,该代码可能会出现意外行为:是否内联调用。
更不用说代码存在架构问题:你有ControlOwner和一堆子控件。我认为你在运行时向ControlOwner添加子控件,然后通过将ControlOwner订阅到childControl事件来尝试对其行为做出反应。这适用于_ButtonClicked等事件。但对Dispose不好。让子控件处理它自己处理,OwnerControl不需要知道它。更不用说它可能一次不存在ChildControl [n] .Dispose被调用。
简而言之: *最好将dispose事件单独留在ChildControl上,并在ChildControl.Dispose中进行全部清理 *没有必要取消订阅活动。事件调度程序将检查订户是否还活着。