Bu nesne ömür boyu genişletme-kapanış bir C # derleyici hata mı?


136

Ben C # derleyici (bu önemli olursa 4.0) kısmında bazı son derece meraklı kod-gen rastladı kapanış (meşru) nesne ömrünü uzatma olasılığı hakkında bir soru cevap .

Bulabildiğim en kısa repro şudur:

  1. Kapsayıcı türde statik bir yöntem çağırırken yerel yakalayan bir lambda oluşturun .
  2. Oluşturulan temsilci başvurusunu içeren nesnenin örnek alanına atayın .

Sonuç: Derleyici, herhangi bir nedeni olmadığında lambda'yı oluşturan nesneye başvuran bir kapatma nesnesi oluşturur - temsilcinin 'iç' hedefi statik bir yöntemdir ve lambda oluşturma nesnesinin örnek üyelerinin buna gerek yoktur temsilci yürütüldüğünde dokunulmamalı (ve dokunulmamalıdır). Derleyici, programcının thissebepsiz yakaladığı gibi davranmaktadır .

class Foo
{
    private Action _field;

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

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

    private static void StaticMethod(double arg) { }
}

Bir sürüm derlemesinden oluşturulan kod ('daha basit' C # olarak ayrıştırılır) şöyle görünür:

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

Bunu gözlemleyin <>4__thisKapatma nesnesinin alanının bir nesne başvurusu ile doldurulduğunu ancak hiçbir zaman okunmadığını (bir neden yoktur).

Peki burada neler oluyor? Dil belirtimi buna izin veriyor mu? Bu bir derleyici hata / tuhaflık mı yoksa kapanışın nesneye başvurması için iyi bir neden var mı (açıkça eksik olduğum)? Bu beni endişelendiriyor çünkü bu, kapanıştan mutlu programcılar (benim gibi), istemeden garip bellek sızıntıları (delegenin bir olay işleyicisi olarak kullanılıp kullanılmadığını hayal et) tanıtmak için bir reçete gibi görünüyor.


19
İlginç. Bana bir böcek gibi görünüyor. (Eğer değer dönerseniz örneğin) bir örneği alanına atamak yoksa Not, o kadar değil yakalama this.
Jon Skeet

15
VS11 Geliştirici önizlemesi ile bunu kopyalayamıyorum. VS2010SP1'de üreyebilir. Sabit gibi görünüyor :)
Lepie

2
Bu VS2008SP1'de de olur. VS2010SP1 için, hem 3.5 hem de 4.0 için olur.
leppie

5
Hmm, bug için geçerli olan çok büyük bir kelime. Derleyici sadece biraz verimsiz kod üretir. Kesinlikle bir sızıntı değil, bu çöp sorunsuz bir şekilde toplanıyor. Zaman uyumsuz uygulama üzerinde çalışırken muhtemelen düzeltildi.
Hans Passant

7
@Hanim, temsilci nesnenin ömrü boyunca hayatta kalırsa, bu sorunsuz bir şekilde çöp toplamak olmaz ve bunun olmasını engelleyen hiçbir şey yoktur.
SoftMemes

Yanıtlar:


24

Bu kesinlikle bir hata gibi görünüyor. Dikkatime sunduğunuz için teşekkürler. Ben bakacağım. Zaten bulunmuş ve düzeltilmiş olması mümkündür.


7

Bir hata ya da gereksiz gibi görünüyor:

Ben IL lang içinde örnek koşmak:

.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

Örnek 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: (Not !! şimdi bu referans gitti!)

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
    }

Örnek 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: (Bu işaretçi geri döndü)

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

Ve her üç durumda da b__0 () yöntemi aynı görünür:

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
    }

Ve her üç durumda da statik bir yönteme referans var, bu yüzden daha garip hale getiriyor. Yani bu litle analys sonra onun bir hata / hayır için iyi diyecekler. !


Bu iç içe sınıf tarafından oluşturulan bir lambda ifadesi içinde bir üst sınıf statik yöntemleri kullanmak için kötü bir fikir olduğunu varsayalım? Sadece Foo.InstanceMethodstatik olup olmadığını merak ediyorum , bu da referans kaldırır mı? Bildiğim için minnettar olurum.
Ivaylo Slavov

1
@Ivaylo: Foo.InstanceMethodAyrıca durağan olsaydı, görünürde hiçbir örnek olmazdı ve bu nedenle thiskapanış tarafından herhangi bir şekilde ele geçirilemezdi.
Ani

1
@Ivaylo Slavov Örnek yöntemi statik olsaydı, o zaman alan statik olmalı, denedim - ve bir 'bu işaretçi' olmayacak.
Niklas

@Niklas, Teşekkürler. Sonuç olarak, lambdalar oluşturmak için statik yöntemlerin bu gereksiz işaretçinin eksikliğini garanti edeceğini düşünüyorum.
Ivaylo Slavov

@Ivaylo Slavov, sanırım .. :)
Niklas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.