关于内存泄漏:这个object-lifetime-extends-closure是C#编译器的bug吗?

Is this object-lifetime-extending-closure a C# compiler bug?

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

我能找到的最短报告如下:

  • 创建一个lambda,该lambda在调用包含类型的静态方法时捕获本地变量。
  • 将生成的委托引用分配给包含对象的实例字段。
  • 结果:编译器创建一个引用创建lambda的对象的闭包对象,当它没有理由时-委托的"内部"目标是一个静态方法,并且在执行委托时,lambda创建对象的实例成员不需要(也不需要)被触摸。实际上,编译器的行为就像程序员无缘无故地捕获了this

    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);
        }
    }

    观察到闭包对象的<>4__this字段填充了一个对象引用,但从未从中读取(没有理由)。

    那这是怎么回事?语言规范允许吗?这是一个编译器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
        }

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