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

时间:2011-12-07 15:04:30

标签: c# memory-leaks closures 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);
         ...
      }
   }
} 

由于delegate捕获变量this._bar,它是否隐式保留B的实例?将B的实例通过事件处理程序引用,并通过A的实例捕获变量吗?

如果_barAttachToAEvent方法的局部变量会不同呢?

因为在我的情况下,A的实例寿命更长,并且远小于B的实例,所以我担心这会导致“内存泄漏”。

3 个答案:

答案 0 :(得分:13)

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以改善情况有一些想法,但没有承诺。

答案 1 :(得分:10)

通过查看编译器生成的代码,这是最容易理解的,类似于:

public void AttachToAEvent()
{
    _foo.AEvent += new EventHandler(this.Handler);
}

[CompilerGenerated]
private void Handler(object sender, EventArgs e)
{
    this.UseBar(this._bar);
}

可以清楚地看到,创建的委托是实例 -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);
    }
}

答案 2 :(得分:0)

如果您向某个事件添加匿名方法并希望遵循该事件,则必须将该事件设置为null或将您的委托存储在列表中,以便以后可以" - ="来自你的活动。

但是,是的,你可以得到一个"内存泄漏"来自附加到事件的委托中引用的对象。