【发布时间】:2009-07-02 20:01:56
【问题描述】:
如果我的软件有两个对象实例,其中一个订阅了另一个的事件。我是否需要在它们成为孤儿之前相互取消订阅,以便垃圾收集器清理它们?或者还有什么其他原因我应该清除事件关系?如果订阅的对象是孤立的,但订阅者不是,反之亦然?
【问题讨论】:
标签: c# .net events garbage-collection
如果我的软件有两个对象实例,其中一个订阅了另一个的事件。我是否需要在它们成为孤儿之前相互取消订阅,以便垃圾收集器清理它们?或者还有什么其他原因我应该清除事件关系?如果订阅的对象是孤立的,但订阅者不是,反之亦然?
【问题讨论】:
标签: c# .net events garbage-collection
是的,你知道。事件发布者持有对对象的引用,并且会阻止它们被垃圾回收。
让我们看一个例子来看看会发生什么。我们有两个班级;一个暴露事件,另一个消耗它:
class ClassA
{
public event EventHandler Test;
~ClassA()
{
Console.WriteLine("A being collected");
}
}
class ClassB
{
public ClassB(ClassA instance)
{
instance.Test += new EventHandler(instance_Test);
}
~ClassB()
{
Console.WriteLine("B being collected");
}
void instance_Test(object sender, EventArgs e)
{
// this space is intentionally left blank
}
}
注意 ClassB 如何不存储对 ClassA 实例的引用;它只是连接了一个事件处理程序。
现在,让我们看看如何收集对象。场景一:
ClassB temp = new ClassB(new ClassA());
Console.WriteLine("Collect 1");
GC.Collect();
Console.ReadKey();
temp = null;
Console.WriteLine("Collect 2");
GC.Collect();
Console.ReadKey();
我们创建一个 ClassB 实例并通过 temp 变量保存对它的引用。它传递了一个 ClassA 的新实例,我们不会在任何地方存储对它的引用,因此它在 ClassB 构造函数完成后立即超出范围。我们让垃圾收集器在 ClassA 超出范围时运行一次,在 ClassB 超出范围时运行一次。输出:
Collect 1
A being collected
Collect 2
B being collected
场景 2:
ClassA temp = new ClassA();
ClassB temp2 = new ClassB(temp);
temp2 = null;
Console.WriteLine("Collect 1");
GC.Collect();
Console.ReadKey();
temp = null;
Console.WriteLine("Collect 2");
GC.Collect();
Console.ReadKey();
创建了一个新的 ClassA 实例,并且对它的引用存储在 temp 变量中。然后创建一个新的 ClassB 实例,将 temp 中的 ClassA 实例传递给它,并将对它的引用存储在 temp2 中。然后我们将 temp2 设置为 null,使 ClassB 实例超出范围。和以前一样,我们让垃圾收集器在每个实例超出范围后运行。输出:
Collect 1
Collect 2
B being collected
A being collected
所以,总结一下;如果暴露事件的实例超出范围,则无论是否连接了事件处理程序,它都可用于垃圾收集。如果一个实例的事件处理程序连接到另一个实例中的事件,则在分离事件处理程序或附加事件处理程序的实例可用于垃圾收集之前,它将无法用于垃圾收集。
【讨论】:
您只需要在对象暴露事件是长期存在的情况下取消挂钩事件,但对象挂钩事件将是短暂的(并得到垃圾收集得相当快)。
在这种情况下,未能取消挂钩将导致内存泄漏,因为您的短期对象将无法被 GC- 因为长期对象中的事件持有一个委托,该委托持有对短期对象的引用。由于该委托仍引用该短期对象,因此无法对其进行垃圾回收。
静态事件根据定义是长期存在的——它们一直存在到程序退出。如果你钩住了一个静态事件,你肯定应该在完成后取消钩住它。
如果两个对象都将成为孤立对象,则无需取消挂钩。
【讨论】:
订阅事件会导致对订阅者的强引用。这是因为,在幕后,事件是委托,而实例方法的委托是对象引用和实际方法的组合。如果您不取消订阅,发布者将继续维护引用,并且只要发布者还活着,订阅对象就永远不会真正成为孤立对象(和 GC'ed)。
反之则不然,即订阅的对象没有对发布者的任何引用。
【讨论】: