C#事件内存泄漏

这些未订阅事件何时发生内存泄漏? 我应该编写析构函数还是实现IDisposable来取消订阅事件?

假设A引用B。 此外,假设你认为你已经完成了B并期望它被垃圾收集。

现在,如果A可以到达 [1], B将不会被垃圾收集,尽管“你已经完成了它”。 从本质上讲,这是一个内存泄漏 [2]

如果B订阅A中的事件,那么我们也有相同的情况: A通过事件处理程序委托引用B。

那么,这什么时候出现问题呢? 仅当引用对象可达时,如上所述。 在这种情况下,当不再使用Foo实例时可能会发生泄漏:

class Foo { Bar _bar; public Foo(Bar bar) { _bar = bar; _bar.Changed += BarChanged; } void BarChanged(object sender, EventArgs e) { } } 

可能存在泄漏的原因是在构造函数中传递的Bar实例可以比使用它的Foo实例具有更长的生命周期。 然后,订阅的事件处理程序可以使Foo保持活动状态。

在这种情况下,您需要提供一种取消订阅事件的方法,以避免内存泄漏。 一种方法是让Foo实现IDisposable 。 这样做的好处是,它清楚地向class级消费者发出信号,表明他需要在完成后调用Dispose() 。 另一种方法是使用单独的Subscribe()Unsubscribe()方法,但这并不能传达类型的期望 – 它们太可选而不能调用并引入时间耦合。

我的建议是:

 class sealed Foo : IDisposable { readonly Bar _bar; bool _disposed; ... public void Dispose() { if (!_disposed) { _disposed = true; _bar.Changed -= BarChanged; } } ... } 

或者:

 class sealed Foo : IDisposable { Bar _bar; ... public void Dispose() { if (_bar != null) { _bar.Changed -= BarChanged; _bar = null; } } ... } 

另一方面,当无法访问引用对象时, 不会发生泄漏:

 class sealed Foo { Bar _bar; public Foo() { _bar = new Bar(); _bar.Changed += BarChanged; } void BarChanged(object sender, EventArgs e) { } } 

在这种情况下,任何Foo实例将始终比其组合的Bar实例更长。 当Foo无法访问时,它的Bar也是如此。 订阅的事件处理程序无法使Foo保持活动状态。 这样做的缺点是,如果Bar是需要在unit testing场景中进行模拟的依赖项,它不能(以任何干净的方式)由消费者显式实例化,但需要注入。

[1] http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

[2] http://en.wikipedia.org/wiki/Memory_leak

事件处理程序包含对声明处理程序的对象的强引用(在委托的Target属性中)。

在删除事件处理程序之前(或者直到拥有该事件的对象不再在任何地方引用),将不会收集包含该处理程序的对象。

您可以通过在不再需要它时删除处理程序来解决此问题(可能在Dispose() )。
终结器无法帮助,因为终结器只能在收集后运行。