这个object-lifetime-extends-closure是一个C#编译器错误吗?

时间:2011-12-07 16:45:50

标签: c# memory-leaks lambda closures object-lifetime

当我遇到C#编译器的一些好奇的代码时,我正在回答一个question关于关闭(合法地)延长对象生命周期的可能性{4.0}如果那很重要)。

我能找到的最短的repro如下:

  1. 创建一个lambda,在调用包含类型的 static 方法时捕获本地。
  2. 将生成的委托引用分配给包含对象的实例字段。
  3. 结果:编译器创建一个闭包对象,该对象引用创建lambda的对象,当它没有理由时 - ' inner'委托的目标是静态方法,并且当委托被执行时,lambda-creation-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字段是用对象引用填充的,但是从不读取(没有理由)。

    那么这里发生了什么?语言规范是否允许它?这是一个编译器错误/奇怪或是否有一个很好的理由(我明显缺少)闭包引用对象?这让我感到焦虑,因为这看起来像是一个关闭快乐的程序员(像我一样)的秘诀,无意中将奇怪的内存泄漏(想象一下,如果委托被用作事件处理程序)到程序中。

2 个答案:

答案 0 :(得分:24)

这肯定看起来像个bug。谢谢你引起我的注意。我会调查一下。它可能已被发现并修复。

答案 1 :(得分:7)

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

我在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

示例2:

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中的

:(注意!!现在这个参考已经消失了!)

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:

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
    }

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