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
的实例捕获变量吗?
如果_bar
是AttachToAEvent
方法的局部变量会不同呢?
因为在我的情况下,A
的实例寿命更长,并且远小于B
的实例,所以我担心这会导致“内存泄漏”。
答案 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或将您的委托存储在列表中,以便以后可以" - ="来自你的活动。
但是,是的,你可以得到一个"内存泄漏"来自附加到事件的委托中引用的对象。