【发布时间】:2012-01-15 04:28:31
【问题描述】:
我在回答 question 关于闭包(合法地)延长对象生命周期的可能性时遇到了一些非常好奇的 C# 编译器代码生成(如果是 4.0)事项)。
我能找到的最短的复制品如下:
- 创建一个 lambda,在调用包含类型的 静态 方法时捕获局部变量。
- 将生成的委托引用分配给包含对象的 instance 字段。
结果:编译器创建了一个闭包对象,它引用了创建 lambda 的对象,当它没有理由时 - 委托的“内部”目标是一个 static 方法,并且执行委托时,不需要(也不会)触及 lambda-creating-object 的实例成员。实际上,编译器的行为就像程序员无缘无故捕获了this。
class Foo
{
private Action _field;
public void InstanceMethod()
{
var capturedVariable = Math.Pow(42, 1);
_field = () => StaticMethod(capturedVariable);
}
private static void StaticMethod(double arg) { }
}
从发布版本生成的代码(反编译为“更简单”的 C#)如下所示:
public void InstanceMethod()
{
<>c__DisplayClass1 CS$<>8__locals2 = new <>c__DisplayClass1();
CS$<>8__locals2.<>4__this = this; // What's this doing here?
CS$<>8__locals2.capturedVariable = Math.Pow(42.0, 1.0);
this._field = new Action(CS$<>8__locals2.<InstanceMethod>b__0);
}
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
// Fields
public Foo <>4__this; // Never read, only written to.
public double capturedVariable;
// Methods
public void <InstanceMethod>b__0()
{
Foo.StaticMethod(this.capturedVariable);
}
}
观察到闭包对象的<>4__this 字段填充了对象引用,但从未从中读取(没有理由)。
那么这里发生了什么?语言规范允许吗?这是一个编译器错误/怪异,还是有充分的理由(我显然错过了)让闭包引用该对象?这让我很焦虑,因为这看起来像是喜欢关闭的程序员(比如我)在不经意间将奇怪的内存泄漏(想象一下,如果委托被用作事件处理程序)引入程序的秘诀。
【问题讨论】:
-
有趣。对我来说似乎是一个错误。请注意,如果您不分配给实例字段(例如,如果您返回值),它不会捕获
this。 -
我无法使用 VS11 开发者预览版重现此内容。可以在 VS2010SP1 中重现。似乎它是固定的:)
-
这也发生在 VS2008SP1 中。对于 VS2010SP1,3.5 和 4.0 都会发生这种情况。
-
嗯,bug 是一个非常大的词,适用于此。编译器只是生成效率稍低的代码。当然不是泄漏,这种垃圾收集没有问题。当他们处理异步实现时,它可能已经修复了。
-
@Hans,如果委托能够在对象的生命周期内存活,那么这不会毫无问题地进行垃圾收集,并且没有什么可以阻止这种情况的发生。
标签: c# memory-leaks lambda closures object-lifetime