如何安全地取消事件处理程序?

7
假设我有一个名为A的类,它可以触发一个名为X的事件。现在我有一个类B,在其方法中,我获取了A的实例并将事件绑定到B中的处理程序:
public void BindEvent(A a)
{
    a.X += AEventHandler;
}

我有三个关于这个的问题。

  • 现在如果我将B实例的引用设置为null,它是否真的不会被垃圾回收,因为垃圾回收器认为它仍在使用(从而在内存中保留一个无用且可能干扰的B副本)?

  • 如果我有另一个对象C(属于类C),其中我有对A的引用称为a(“this.a = new A()”)。然后我调用“b.BindEvent(this.a)”,并在C中将对a的引用设置为null(“this.a = null”)。因为通过b中的事件引用了A,所以这样做会使A的副本保留在内存中吗?

  • 如果以上任何一种或两种情况都是正确的,我该如何最好地绕过这些问题?如果我有整个事件处理程序列表(例如10行像“a.SomeEvent += SomeMethod”),我应该清除它们吗(“a.SomeEvent -= SomeMethod”)?在代码的哪个时间或位置应该执行这些操作?

嗯,这有点模糊,但我不确定如何更好地解释。如果需要更详细的说明,请留言。

4个回答

8

所以: A 是发布者,B 是订阅者?

第一个要点:如果 B 是具有 AEventHandler 的实例,则它仍在使用中,因此,不,除非 a 实例是不可访问的,否则它不会被收集。

第二个要点:啥?(再读一遍...)如果 AB 实例都不可访问,则它们将被垃圾回收;事件无关紧要。如果 A 是可访问的,则 B 将保持活动状态。但是,事件订阅从不使 A 保持活动状态;它是单向的...A 可以使 B 保持活动状态,但 B 不会使 A 保持活动状态。这样可以吗?

第三个要点:在大多数情况下,这两个事物的寿命相似,因此这不是问题。只有当发布事件的事物的生存时间比处理程序的事物长得多时,它才成为问题。在这种情况下,您只需要严格清理自己的代码-例如:a.X - = AEventHandler。特别是,static 事件因此原因是邪恶的


4

在销毁与事件处理程序相关联的类实例之前,您应该真正解除绑定事件处理程序。(以您的代码为例。)

public void UnbindEvent(A a)
{
    a.X -= AEventHandler;
}

我想问一下,你为什么要将类变量设置为null?该做法在IT技术中常见。

2
  1. 正确。
  2. 正确。
  3. 为什么要规避这种行为?这是GC设计的工作方式。如果您需要在每个对象被销毁时进行一些清理工作,请使用Dispose模式。

我非常确定2是不正确的。首先,事件从不保持发布者(A)处于活动状态 - 其次,任何东西都无法看到A...因此数据是断开的并被收集。 - Marc Gravell

1

是的,事件是引用,如果您不取消注册,则实现事件处理程序的对象将无法进行垃圾回收。

您可以这样做:

如果 A 知道何时不再使用所有已注册的事件,则删除它们。

class A
{
  // clearing all registrations
  private void ClearEvents()
  {
    X = null;
  }
}

或者你可以在B中注销,如果B知道它不再使用a。为了能够注销,您需要保留对a的引用。

您还可以实现IDisposable接口。

class B : IDisposable
{
  private A registeredToA;

  public void BindEvent(A a)
  {
    registeredToA = a;
    registeredToA.X += AEventHandler;
  }

  public void Dispose()
  {
    registeredToA.x -= AEventHandler;
  } 
}

这是对你的代码的重大更改,因为B始终需要被处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接