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

class A { public event EventHandler AEvent; } class B { private A _foo; private int _bar; public void AttachToAEvent() { _foo.AEvent += delegate() { ... UseBar(_bar); ... } } } 

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

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

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

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

 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方法的局部变量,它会有所不同吗?

是的,它会,因为捕获的变量将成为本地而不是本地。 但假设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); } } 

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的“异步/等待”function也可能会加剧当地人的寿命比你预期的更长的情况。 我们没有人对此感到兴奋,但正如他们所说,完美是令人敬畏的敌人。 我们对如何调整异步块的codegen以改善情况有一些想法,但没有任何承诺。

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

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