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
事件处理程序包含对声明处理程序的对象的强引用(在委托的Target
属性中).
在删除事件处理程序之前(或者直到拥有事件的对象不再在任何地方引用),将不会收集包含处理程序的对象.
您可以通过在不再需要它时删除处理程序(可能在Dispose()
)中来解决此问题.
终结器无法帮助,因为终结器只能在收集后运行.
归档时间: |
|
查看次数: |
9804 次 |
最近记录: |