Чому деякі вирази C # лямбда компілюються до статичних методів?


122

Як ви бачите в коді нижче, я оголосив Action<>об'єкт змінною.

Хто-небудь, будь ласка, дайте мені знати, чому цей делегат методу дії поводиться як статичний метод?

Чому він повертається trueв наступному коді?

Код:

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

    Console.WriteLine(actionMethod.Method.IsStatic);

    Console.Read();
}

Вихід:

Приклад виведення вибірки

Відповіді:


153

Це найімовірніше, тому що немає закриттів, наприклад:

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

Це виведе falseдля withClosureі trueдля withoutClosure.

Коли ви використовуєте лямбда-вираз, компілятор створює невеликий клас, щоб містити ваш метод, це компілюватиметься до чогось подібного (фактична реалізація, швидше за все, незначно змінюється):

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

Ви можете бачити, що отримані Action<string>екземпляри фактично вказують на методи цих створених класів.


4
+1. Можна підтвердити - без закриття вони є ідеальними кандидатами на staticметоди.
Саймон Уайтхед

3
Я просто збирався припустити, що це питання потребує певного розширення, я повернувся і ось воно було. Дуже інформативний - чудово бачити, що компілятор робить під обкладинками.
Ліат

4
@Liath Ildasmдуже корисний для розуміння того, що насправді відбувається, я схильний використовувати ILвкладку LINQPadдля вивчення невеликих зразків.
Луказоїд

@Lukazoid Скажіть, будь ласка, як ви отримали цей вихід компілятора? ILDASM не дасть такого виходу. За допомогою будь-якого інструменту АБО програмного забезпечення?
nunu

8
@nunu У цьому прикладі я використав ILвкладку LINQPadта зробив висновок C #. Деякі варіанти отримання фактичного еквівалента C # компільованого виводу полягають у використанні ILSpyабо Reflectorна компільованій збірці, вам, швидше за все, потрібно буде відключити деякі параметри, які намагатимуться відображати лямбда, а не класи, створені компілятором.
Луказоїд

20

"Метод дії" є статичним лише як побічний ефект від реалізації. Це випадок анонімного методу без захоплених змінних. Оскільки немає захоплених змінних, метод не має додаткових вимог протягом життя, ніж вимоги до локальних змінних загалом. Якщо він посилався на інші локальні змінні, його термін служби поширюється на термін експлуатації цих інших змінних (див. Розділ L.1.7, Локальні змінні та сек. N.15.5.1, Захоплені зовнішні змінні в специфікації C # 5.0).

Зауважте, що специфікація C # говорить лише про те, що анонімні методи перетворюються на "дерева виразів", а не в "анонімні класи". Хоча дерево виразів може бути представлено у вигляді додаткових класів C #, наприклад, у компіляторі Microsoft, ця реалізація не потрібна (як це підтверджено у розділі M.5.3 у специфікації C # 5.0). Тому не визначено, статична анонімна функція чи ні. Більше того, розділ K.6 залишає багато відкритих щодо деталей дерев виразів.


2
+1, з цієї причини, швидше за все, не слід покладатися на цю поведінку; це дуже детальна реалізація.
Луказоїд

18

Поведінка кешування делегата була змінена в Росліні. Раніше, як було сказано, будь-який лямбда-вираз, який не фіксував змінні, був складений у staticметод на сайті виклику. Рослін змінив таку поведінку. Тепер будь-яка лямбда, яка фіксує змінні чи ні, перетворюється на клас відображення:

З огляду на цей приклад:

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

Вихідний вихід компілятора:

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

Рослін:

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

Делегатські зміни поведінки кешування в Росліні розповідають про те, чому цю зміну було здійснено.


2
Дякую, мені було цікаво, чому метод мого Func <int> f = () => 5 не був статичним
vc 74,

2

Що стосується C # 6, це завжди буде стандартним методом екземплярів зараз, і ніколи не буде статичним (тому actionMethod.Method.IsStaticзавжди буде помилковим).

Дивіться тут: Чому лямбда без захоплення змінилася зі статичного в C # 5 на метод екземпляра в C # 6?

і тут: Різниця в статичній оцінці виразника лямбда-компресатора CSC і Roslyn?


1

Метод не має закриттів, а також посилається на сам статичний метод (Console.WriteLine), тому я б очікував, що він буде статичним. Метод оголосить закритий анонімний тип для закриття, але в цьому випадку це не потрібно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.