C#事件内存泄漏

fex*_*fex 14 c# memory-leaks

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

Joh*_*ell 33

比方说,一个引用.此外,假设你认为你已经完成了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) { }
}
Run Code Online (Sandbox Code Playgroud)

可能存在泄漏的原因是在构造函数中传递的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;
        }
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

或者:

class sealed Foo : IDisposable
{
    Bar _bar;

    ...

    public void Dispose()
    {
        if (_bar != null)
        {
            _bar.Changed -= BarChanged;
            _bar = null;
        }
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

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

class sealed Foo
{
    Bar _bar;

    public Foo()
    {
        _bar = new Bar();
        _bar.Changed += BarChanged;
    }

    void BarChanged(object sender, EventArgs e) { }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 循环引用不会影响垃圾收集。原因在这里完美描述:http://stackoverflow.com/a/400727/6345 (2认同)

SLa*_*aks 6

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

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

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

  • @fex:您应该花时间计算每个对象的持续时间,并在必要时删除.如果您想制作一个可靠的程序,了解您希望它如何工作非常重要. (2认同)