12
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,所以我担心这样做会导致“内存泄漏”。

4

3 回答 3

15

阿尼的回答是正确的。总结和补充一些细节:

由于委托捕获变量 this._bar,它是否隐含地持有 B 的实例?

是的。“这个”被捕获。

是否会通过事件处理程序引用 B 的实例并由 A 的实例捕获变量?

是的。

如果 _bar 是 AttachToAEvent 方法的局部变量,会有所不同吗?

是的。在这种情况下,闭包对象将保留本地;local 将被实现为闭包的一个字段。

由于在我的情况下,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 的“async/await”特性也可能会加剧当地人最终活得比你预期更长的情况。我们没有人对此感到兴奋,但正如他们所说,完美是令人敬畏的敌人。我们对如何调整异步块的代码生成以改善情况有一些想法,但没有承诺。

于 2011-12-07T16:10:38.513 回答
10

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

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

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

可以清楚地看到,创建的委托是一个实例-委托(以对象上的实例方法为目标),因此必须持有对该对象实例的引用。

由于委托捕获变量 this._bar,它是否隐含地持有 B 的实例?

实际上,匿名方法只捕获this(not 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);
    }
}
于 2011-12-07T15:54:41.180 回答
0

如果您将匿名方法添加到事件并希望尊重它,则必须将该事件设置为 null 或将您的委托存储在列表中,以便以后可以从您的事件中“-=”它。

但是,是的,您可以从附加到事件的委托中引用的对象获得“内存泄漏”。

于 2011-12-07T15:14:31.433 回答