Is this object-lifetime-extending-closure a C# compiler bug?
我回答了一个关于闭包(合法地)延长对象生命周期的可能性的问题,当时我遇到了C编译器(如果这很重要的话,是4.0)中一些非常奇怪的代码生成。
我能找到的最短报告如下:
结果:编译器创建一个引用创建lambda的对象的闭包对象,当它没有理由时-委托的"内部"目标是一个静态方法,并且在执行委托时,lambda创建对象的实例成员不需要(也不需要)被触摸。实际上,编译器的行为就像程序员无缘无故地捕获了
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Foo { private Action _field; public void InstanceMethod() { var capturedVariable = Math.Pow(42, 1); _field = () => StaticMethod(capturedVariable); } private static void StaticMethod(double arg) { } } |
从发布版本生成的代码(反编译为"简单"C)如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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); } } |
观察到闭包对象的
那这是怎么回事?语言规范允许吗?这是一个编译器bug/odty,还是有一个很好的理由(很明显我没有)让闭包引用对象?这让我感到焦虑,因为这看起来像是一个让程序员(像我一样)不自觉地将奇怪的内存泄漏(假设委托被用作事件处理程序)引入到程序中的方法。
看起来像个虫子。谢谢你提醒我。我会调查的。可能已经找到并修复了它。
这似乎是一个错误或不必要的:
我在《爱浪》中为你树立了榜样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | .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 |
例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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) { } } } |
在CL:(音符)!!现在该引用已不存在!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 } |
例3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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中:(此指针返回)
1 2 3 | 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_uuu 0()-看起来都一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 } |
在这三种情况下,都有一个对静态方法的引用,因此它使奇怪的。所以在这个小小的分析之后,我会说这是一个错误/没有好处。!