14

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

4

2 回答 2

33

假设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。这样做的好处是它清楚地向类消费者发出信号,他需要在完成后调用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) { }
}

In this case any Foo instance will always outlive its composed Bar instance. When a Foo is unreachable, so will its Bar be. The subscribed event handler cannot keep the Foo alive here. The downside of this is that if Bar is a dependency in need of being mocked in unit testing scenarios, it can't (in any clean way) be explicitly instantiated by the consumer, but needs to be injected.

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

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

于 2012-08-26T20:48:28.980 回答
6

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

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

您可以通过在不再需要处理程序时(可能在 中Dispose())删除处理程序来解决此问题。
终结器无济于事,因为终结器只有在它被收集后才会运行。

于 2012-08-26T20:19:53.603 回答