Dam*_*mir 12 c# closures memory-leaks anonymous-methods object-lifetime
class A
{
public event EventHandler AEvent;
}
class B
{
private A _foo;
private int _bar;
public void AttachToAEvent()
{
_foo.AEvent += delegate()
{
...
UseBar(_bar);
...
}
}
}
Run Code Online (Sandbox Code Playgroud)
由于delegate
捕获变量this._bar
,它是否隐含地保持实例B
?将B
通过事件处理程序和捕获的变量引用实例A
吗?
如果_bar
是方法的局部变量,它会有所不同AttachToAEvent
吗?
因为在我的情况下,A
生命的实例远远长于并且远小于实例B
,我担心通过这样做导致"内存泄漏".
Eri*_*ert 14
Ani的回答是正确的.总结并添加一些细节:
由于委托捕获变量this._bar,它是否隐式持有B的实例?
是."这个"被捕获了.
B的实例是否会通过事件处理程序引用并通过A的实例捕获变量?
是.
如果_bar是AttachToAEvent方法的局部变量,它会有所不同吗?
是.在这种情况下,封闭物体将保持在当地; 本地将被视为封闭的领域.
因为在我的情况下A的实例寿命更长并且远小于B的实例,所以我担心通过这样做导致"内存泄漏".
你是绝对正确的担心.你的情况已经很糟糕,但实际上当你有两个匿名函数在运行时情况可能会更糟.现在,同一局部变量声明空间中的所有匿名函数共享一个公共闭包,这意味着所有已关闭的外部变量(包括"this")的生命周期都会延长到所有这些变量的最长寿命.有关详细信息,请参阅有关该主题的文章
http://blogs.msdn.com/b/ericlippert/archive/2007/06/06/fyi-c-and-vb-closures-are-per-scope.aspx
我们希望在假设的未来版本的C#中解决这个问题; 我们可以更好地分割闭包,而不是创建一个大闭包.然而,这不会很快发生.
此外,C#5的"异步/等待"功能也可能会加剧当地人的寿命超过你的期望.我们没有人对此感到兴奋,但正如他们所说,完美是令人敬畏的敌人.我们对如何调整异步块的codegen以改善情况有一些想法,但没有任何承诺.
Ani*_*Ani 10
通过查看编译器生成的代码,这是最简单的理解,类似于:
public void AttachToAEvent()
{
_foo.AEvent += new EventHandler(this.Handler);
}
[CompilerGenerated]
private void Handler(object sender, EventArgs e)
{
this.UseBar(this._bar);
}
Run Code Online (Sandbox Code Playgroud)
可以清楚地看到,创建的委托是一个实例 -delegate(以对象上的实例方法为目标),因此必须保存对此对象实例的引用.
由于委托捕获变量this._bar,它是否隐式持有B的实例?
实际上,匿名方法只捕获this
(不this._bar
).从生成的代码中可以看出,构造的委托确实会保存对B
实例的引用.它必须; 如果代表执行,该字段还可以按需读取吗?请记住,捕获变量,而不是值.
因为在我的情况下A的实例寿命更长并且远小于B的实例,所以我担心通过这样做导致"内存泄漏".
是的,你有充分的理由.只要A
实例可以访问,B
事件订阅者仍然可以访问.如果你不想对弱事件感兴趣,你需要重写它,以便在不再需要处理程序时取消注册.
如果_bar是AttachToAEvent方法的局部变量,它会有所不同吗?
是的,它会,因为捕获的变量将成为bar
本地而不是this
.但假设这UseBar
是一个实例方法,你的"问题"(如果你想以这种方式想到它)刚刚变得更糟.编译器现在需要生成一个"记住"本地和包含B
对象实例的事件监听器.
这是通过创建一个闭包对象并使它(实际上是它的实例方法)成为委托的目标来实现的.
public void AttachToAEvent(int _bar)
{
Closure closure = new Closure();
closure._bar = _bar;
closure._bInstance = this;
_foo.AEvent += new EventHandler(closure.Handler);
}
[CompilerGenerated]
private sealed class Closure
{
public int _bar;
public B _bInstance;
public void Handler(object sender , EventArgs e)
{
_bInstance.UseBar(this._bar);
}
}
Run Code Online (Sandbox Code Playgroud)