在匿名代表中捕获的私有字段

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)