Mengapa beberapa ekspresi lambda C # dikompilasi ke metode statis?

122

Seperti yang Anda lihat pada kode di bawah ini, saya telah mendeklarasikan Action<>objek sebagai variabel.

Adakah yang bisa memberi tahu saya mengapa delegasi metode tindakan ini berperilaku seperti metode statis?

Mengapa itu kembali truedalam kode berikut?

Kode:

public static void Main(string[] args)
{
    Action<string> actionMethod = s => { Console.WriteLine("My Name is " + s); };

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

Keluaran:

contoh keluaran sampel

nunu
sumber

Jawaban:

153

Ini kemungkinan besar karena tidak ada penutupan, misalnya:

int age = 25;
Action<string> withClosure = s => Console.WriteLine("My name is {0} and I am {1} years old", s, age);
Action<string> withoutClosure = s => Console.WriteLine("My name is {0}", s);
Console.WriteLine(withClosure.Method.IsStatic);
Console.WriteLine(withoutClosure.Method.IsStatic);

Ini akan menampilkan falseuntuk withClosuredan trueuntuk withoutClosure.

Saat Anda menggunakan ekspresi lambda, kompilator membuat kelas kecil untuk menampung metode Anda, ini akan dikompilasi menjadi sesuatu seperti berikut (implementasi sebenarnya kemungkinan besar sedikit berbeda):

private class <Main>b__0
{
    public int age;
    public void withClosure(string s)
    {
        Console.WriteLine("My name is {0} and I am {1} years old", s, age)
    }
}

private static class <Main>b__1
{
    public static void withoutClosure(string s)
    {
        Console.WriteLine("My name is {0}", s)
    }
}

public static void Main()
{
    var b__0 = new <Main>b__0();
    b__0.age = 25;
    Action<string> withClosure = b__0.withClosure;
    Action<string> withoutClosure = <Main>b__1.withoutClosure;
    Console.WriteLine(withClosure.Method.IsStatic);
    Console.WriteLine(withoutClosure.Method.IsStatic);
}

Anda dapat melihat Action<string>instance yang dihasilkan benar-benar mengarah ke metode pada class yang dihasilkan ini.

Lukazoid
sumber
4
+1. Dapat mengonfirmasi - tanpa penutupan, mereka adalah kandidat yang tepat untuk staticmetode tersebut.
Simon Whitehead
3
Saya hanya akan menyarankan bahwa pertanyaan ini memerlukan beberapa perluasan, saya kembali dan itu dia. Sangat informatif - sangat bagus untuk melihat apa yang dilakukan kompilator di balik layar.
Liath
4
@Liath Ildasmsangat berguna untuk memahami apa yang sebenarnya terjadi, saya cenderung menggunakan ILtab dari LINQPaduntuk memeriksa sampel kecil.
Lukazoid
@ Lukazoid Tolong beritahu kami bagaimana Anda mendapatkan keluaran kompilator ini? ILDASM tidak akan memberikan hasil seperti itu .. Dengan alat ATAU perangkat lunak apa saja?
nunu
8
@nunu Dalam contoh ini, saya menggunakan ILtab LINQPaddan menyimpulkan C #. Beberapa opsi untuk mendapatkan C # yang sebenarnya setara dari output yang dikompilasi akan menggunakan ILSpyatau Reflectorpada rakitan yang dikompilasi, Anda kemungkinan besar perlu menonaktifkan beberapa opsi yang akan mencoba menampilkan lambda dan bukan kelas yang dihasilkan kompilator.
Lukazoid
20

"Metode tindakan" bersifat statis hanya sebagai efek samping dari implementasi. Ini adalah kasus metode anonim tanpa variabel yang ditangkap. Karena tidak ada variabel yang ditangkap, metode ini tidak memiliki persyaratan umur tambahan selain variabel lokal pada umumnya. Jika ia mereferensikan variabel lokal lain, masa pakainya meluas ke masa pakai variabel lain tersebut (lihat bagian L.1.7, Variabel lokal , dan bagian N.15.5.1, Variabel luar yang ditangkap , dalam spesifikasi C # 5.0).

Perhatikan bahwa spesifikasi C # hanya berbicara tentang metode anonim yang diubah menjadi "pohon ekspresi", bukan "kelas anonim". Sementara pohon ekspresi dapat direpresentasikan sebagai kelas C # tambahan, misalnya, dalam kompilator Microsoft, implementasi ini tidak diperlukan (seperti yang diakui oleh bagian M.5.3 dalam spesifikasi C # 5.0). Oleh karena itu, tidak ditentukan apakah fungsi anonim itu statis atau tidak. Selain itu, bagian K.6 daun lebih terbuka untuk detail pohon ekspresi.

Peter O.
sumber
2
+1 perilaku ini kemungkinan besar tidak boleh diandalkan, karena alasan yang disebutkan; ini merupakan detail implementasi.
Lukazoid
18

Perilaku cache delegasi diubah di Roslyn. Sebelumnya, seperti yang dinyatakan, setiap ekspresi lambda yang tidak menangkap variabel dikompilasi menjadi staticmetode di situs panggilan. Roslyn mengubah perilaku ini. Sekarang, lambda apa pun, yang menangkap variabel atau tidak, diubah menjadi kelas tampilan:

Diberikan contoh ini:

public class C
{
    public void M()
    {
        var x = 5;
        Action<int> action = y => Console.WriteLine(y);
    }
}

Keluaran kompiler asli:

public class C
{
    [CompilerGenerated]
    private static Action<int> CS$<>9__CachedAnonymousMethodDelegate1;
    public void M()
    {
        if (C.CS$<>9__CachedAnonymousMethodDelegate1 == null)
        {
            C.CS$<>9__CachedAnonymousMethodDelegate1 = new Action<int>(C.<M>b__0);
        }
        Action<int> arg_1D_0 = C.CS$<>9__CachedAnonymousMethodDelegate1;
    }
    [CompilerGenerated]
    private static void <M>b__0(int y)
    {
        Console.WriteLine(y);
    }
}

Roslyn:

public class C
{
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0
    {
        public static readonly C.<>c__DisplayClass0 CS$<>9__inst;
        public static Action<int> CS$<>9__CachedAnonymousMethodDelegate2;
        static <>c__DisplayClass0()
        {
            // Note: this type is marked as 'beforefieldinit'.
            C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0();
        }
        internal void <M>b__1(int y)
        {
            Console.WriteLine(y);
        }
    }
    public void M()
    {
        Action<int> arg_22_0;
        if (arg_22_0 = C.
                       <>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null)
        {
            C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 =
          new Action<int>(C.<>c__DisplayClass0.CS$<>9__inst.<M>b__1);
        }
    }
}

Delegasi perubahan perilaku cache di Roslyn berbicara tentang mengapa perubahan ini dibuat.

Yuval Itzchakov
sumber
2
Terima kasih, saya bertanya-tanya mengapa metode Func <int> f = () => 5 saya tidak statis
vc 74
1

Metode ini tidak memiliki penutupan dan juga mereferensikan metode statis itu sendiri (Console.WriteLine), jadi saya berharap itu menjadi statis. Metode tersebut akan mendeklarasikan tipe anonim yang melingkupi penutupan, tetapi dalam hal ini tidak diperlukan.

Mel Padden
sumber