这个对象-生命周期-扩展-闭包是 C # 编译器的错误吗?

我正在回答一个关于闭包(合法地)扩展对象生命周期的可能性的 有个问题问题,这时我在 C # 编译器中遇到了一些 非常奇怪的代码生成器(如果有问题的话,是4.0)。

我能找到的最短的复制品如下:

  1. 创建一个 lambda,该 lambda 在调用包含类型的 静电干扰方法时捕获局部。
  2. 将生成的委托引用分配给包含对象的 例子字段。

结果: 编译器创建了一个闭包对象来引用创建 lambda 的对象,当它没有理由这样做的时候——委托的“内部”目标是一个 静电干扰方法,当委托被执行时,lambda 创建对象的实例成员不需要(也不需要)被触摸。实际上,编译器的行为就像程序员无缘无故地捕获了 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字段用对象引用填充,但从不从中读取(没有原因)。

这是怎么回事?语言规范允许吗?这是一个编译器错误/怪异还是有一个很好的理由(我显然遗漏了)闭包引用该对象?这让我感到焦虑,因为这看起来像是一个让喜欢闭包的程序员(像我一样)不知不觉地将奇怪的内存泄漏(想象一下如果委托被用作事件处理程序)引入程序的处方。

4051 次浏览

它似乎是一个错误或不必要的:

我在 IL lang 中运行你的例子:

.method public hidebysig
instance void InstanceMethod () cil managed
{
// Method begins at RVA 0x2074
// Code size 63 (0x3f)
.maxstack 4
.locals init (
[0] class ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'   'CS$<>8__locals2'
)


IL_0000: newobj instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldarg.0
IL_0008: stfld class ConsoleApplication1.Program/Foo ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'<>4__this' //Make ref to this
IL_000d: nop
IL_000e: ldloc.0
IL_000f: ldc.r8 42
IL_0018: ldc.r8 1
IL_0021: call float64 [mscorlib]System.Math::Pow(float64, float64)
IL_0026: stfld float64 ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::capturedVariable
IL_002b: ldarg.0
IL_002c: ldloc.0
IL_002d: ldftn instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'<InstanceMethod>b__0'()
IL_0033: newobj instance void [mscorlib]System.Action::.ctor(object, native int)
IL_0038: stfld class [mscorlib]System.Action ConsoleApplication1.Program/Foo::_field
IL_003d: nop
IL_003e: ret
} // end of method Foo::InstanceMethod

例子二:

class Program
{
static void Main(string[] args)
{
}




class Foo
{
private Action _field;


public void InstanceMethod()
{
var capturedVariable = Math.Pow(42, 1);


_field = () => Foo2.StaticMethod(capturedVariable);  //Foo2


}


private static void StaticMethod(double arg) { }
}


class Foo2
{


internal static void StaticMethod(double arg) { }
}




}

In cl: (注意! ! 现在这个引用不见了!)

public hidebysig
instance void InstanceMethod () cil managed
{
// Method begins at RVA 0x2074
// Code size 56 (0x38)
.maxstack 4
.locals init (
[0] class ConsoleApplication1.Program/Foo/'<>c__DisplayClass1' 'CS$<>8__locals2'
)


IL_0000: newobj instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::.ctor()
IL_0005: stloc.0
IL_0006: nop //No this pointer
IL_0007: ldloc.0
IL_0008: ldc.r8 42
IL_0011: ldc.r8 1
IL_001a: call float64 [mscorlib]System.Math::Pow(float64, float64)
IL_001f: stfld float64 ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::capturedVariable
IL_0024: ldarg.0 //No This ref
IL_0025: ldloc.0
IL_0026: ldftn instance void ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'<InstanceMethod>b__0'()
IL_002c: newobj instance void [mscorlib]System.Action::.ctor(object, native int)
IL_0031: stfld class [mscorlib]System.Action ConsoleApplication1.Program/Foo::_field
IL_0036: nop
IL_0037: ret
}

例子三:

class Program
{
static void Main(string[] args)
{
}


static void Test(double arg)
{


}


class Foo
{
private Action _field;


public void InstanceMethod()
{
var capturedVariable = Math.Pow(42, 1);


_field = () => Test(capturedVariable);


}


private static void StaticMethod(double arg) { }
}




}

在 IL 中: (此指针返回)

IL_0006: ldloc.0
IL_0007: ldarg.0
IL_0008: stfld class ConsoleApplication1.Program/Foo ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::'<>4__this' //Back again.

在这三种情况下,方法 b _ _ 0()看起来是一样的:

instance void '<InstanceMethod>b__0' () cil managed
{
// Method begins at RVA 0x2066
// Code size 13 (0xd)
.maxstack 8


IL_0000: ldarg.0
IL_0001: ldfld float64 ConsoleApplication1.Program/Foo/'<>c__DisplayClass1'::capturedVariable
IL_0006: call void ConsoleApplication1.Program/Foo::StaticMethod(float64) //Your example
IL_0006: call void ConsoleApplication1.Program/Foo2::StaticMethod(float64)//Example 2
IL_0006: call void ConsoleApplication1.Program::Test(float64) //Example 3
IL_000b: nop
IL_000c: ret
}

在这三种情况下,都有一个对静态方法的引用,所以它使它更 奇怪。所以在这个小分析之后,我会说这是一个错误/没有好处!

看起来像只虫子。谢谢你提醒我。我会调查的。它可能已经被找到并修复了。