Là đối tượng kéo dài suốt đời đóng cửa này là một lỗi trình biên dịch C #?


136

Tôi đã trả lời một câu hỏi về khả năng đóng cửa (một cách hợp pháp) kéo dài tuổi thọ đối tượng khi tôi gặp một số mã gen cực kỳ tò mò trên một phần của trình biên dịch C # (4.0 nếu vấn đề đó).

Đoạn repro ngắn nhất tôi có thể tìm thấy như sau:

  1. Tạo một lambda bắt một cục bộ trong khi gọi một phương thức tĩnh của kiểu chứa.
  2. Chỉ định tham chiếu đại biểu được tạo cho trường đối tượng chứa.

Kết quả: Trình biên dịch tạo một đối tượng bao đóng tham chiếu đến đối tượng đã tạo lambda, khi nó không có lý do gì - mục tiêu 'bên trong' của đại biểu là một phương thức tĩnh và các thành viên thể hiện của đối tượng tạo lambda không cần được (và không) chạm vào khi đại biểu được thực thi. Thực tế, trình biên dịch đang hoạt động giống như lập trình viên đã nắm bắt thismà không có lý do.

class Foo
{
    private Action _field;

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

        _field = () => StaticMethod(capturedVariable);
    }

    private static void StaticMethod(double arg) { }
}

Mã được tạo từ bản dựng phát hành (được dịch ngược thành 'C # đơn giản hơn) trông như thế này:

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

Quan sát <>4__thistrường của đối tượng đóng được điền với tham chiếu đối tượng nhưng không bao giờ được đọc từ đó (không có lý do).

Vậy chuyện gì đang xảy ra ở đây? Liệu đặc tả ngôn ngữ cho phép nó? Đây có phải là một lỗi / kỳ lạ của trình biên dịch hay có một lý do chính đáng (mà tôi rõ ràng là thiếu) cho việc đóng cửa để tham chiếu đối tượng? Điều này khiến tôi lo lắng vì đây giống như một công thức cho các lập trình viên đóng cửa hạnh phúc (như tôi) vô tình đưa ra những rò rỉ bộ nhớ lạ (hãy tưởng tượng nếu đại biểu được sử dụng như một người xử lý sự kiện) vào các chương trình.


19
Hấp dẫn. Trông giống như một lỗi với tôi. Lưu ý rằng nếu bạn không gán cho một trường đối tượng (ví dụ: nếu bạn trả về giá trị), thì nó sẽ không bắt được this.
Jon Skeet

15
Tôi không thể repro điều này với bản xem trước Nhà phát triển VS11. Có thể repro trong VS2010SP1. Có vẻ như nó đã được sửa :)
leppie

2
Điều này cũng xảy ra trong VS2008SP1. Đối với VS2010SP1, nó xảy ra cho cả 3.5 và 4.0.
leppie

5
Hmm, bug là một từ rất lớn để áp dụng cho điều này. Trình biên dịch chỉ tạo mã hơi kém hiệu quả. Chắc chắn không phải là một rò rỉ, rác này thu thập mà không có vấn đề. Nó có thể đã được sửa khi họ làm việc trên triển khai async.
Hans Passant

7
@Hans, điều này sẽ không thu gom rác mà không có vấn đề gì nếu đại biểu tồn tại suốt đời của đối tượng và không có gì ngăn cản điều này xảy ra.
SoftMeme

Câu trả lời:


24

Điều đó chắc chắn trông giống như một lỗi. Cám ơn bạn đã khiến tôi chú ý tới việc này. Tôi sẽ xem xét nó. Có thể là nó đã được tìm thấy và sửa chữa.


7

Nó dường như là một lỗi hoặc không cần thiết:

Tôi chạy bạn mẫu mực trong 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

Ví dụ 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) { }
    }


}

trong cl: (Lưu ý !! bây giờ tài liệu tham khảo này đã biến mất!)

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
    }

Ví dụ 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) { }
    }


}

trong IL: (Con trỏ này đã trở lại)

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

Và trong cả ba trường hợp, phương thức-b__0 () - trông giống nhau:

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
    }

Và trong cả 3 trường hợp đều có tham chiếu đến một phương thức tĩnh, vì vậy nó làm cho nó trở nên kỳ quặc hơn. Vì vậy, sau khi phân tích litle này, tôi sẽ nói đó là một lỗi / không tốt. !


Tôi cho rằng điều này có nghĩa là ý tưởng BAD sử dụng các phương thức tĩnh từ một lớp cha bên trong một biểu thức lambda được tạo bởi lớp lồng nhau? Tôi chỉ tự hỏi nếu Foo.InstanceMethodđược thực hiện tĩnh, điều này sẽ loại bỏ các tài liệu tham khảo? Tôi rất biết ơn để biết.
Ivaylo Slavov

1
@Ivaylo: Nếu Foo.InstanceMethodcũng là tĩnh, sẽ không có trường hợp nào trong tầm nhìn, và do đó không có cách nào thisđể bị bắt bởi đóng cửa.
Ani

1
@Ivaylo Slavov Nếu phương thức cá thể là tĩnh, thì trường phải tĩnh, tôi đã thử - và sẽ không có 'con trỏ này'.
Niklas

@Niklas, Cảm ơn bạn. Để kết luận, tôi cho rằng các phương thức tĩnh để tạo lambdas sẽ đảm bảo thiếu con trỏ không cần thiết này.
Ivaylo Slavov

@Ivaylo Slavov, Đoán vậy .. :)
Niklas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.