Почему некоторые лямбда-выражения 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>экземпляры фактически указывают на методы этих сгенерированных классов.

Lukazoid
источник
4
+1. Могу подтвердить - без закрытия они идеальные кандидаты для staticметодов.
Саймон Уайтхед
3
Я просто собирался предположить, что этот вопрос нуждается в некотором расширении, я вернулся, и вот оно. Очень информативно - приятно видеть, что компилятор делает под прикрытием.
Liath
4
@Liath Ildasmдействительно полезен для понимания того, что на самом деле происходит, я обычно использую ILвкладку LINQPadдля изучения небольших выборок.
Lukazoid 01
@Lukazoid Не могли бы вы рассказать нам, как вы получили этот вывод компилятора? ILDASM не даст такого вывода .. С помощью какого-либо инструмента ИЛИ программного обеспечения?
nunu 01
8
@nunu В этом примере я использовал ILвкладку LINQPadи вывел C #. Некоторые параметры для получения фактического эквивалента C # скомпилированного вывода могут заключаться в использовании ILSpyили Reflectorна скомпилированной сборке, вам, скорее всего, потребуется отключить некоторые параметры, которые будут пытаться отображать лямбды, а не классы, созданные компилятором.
Lukazoid 01
20

«Метод действия» статичен только как побочный эффект реализации. Это случай анонимного метода без захваченных переменных. Поскольку фиксированные переменные отсутствуют, у метода нет дополнительных требований к сроку службы, кроме общих требований к локальным переменным. Если он ссылался на другие локальные переменные, его время жизни расширяется до времени жизни этих других переменных (см. Раздел L.1.7, Локальные переменные и раздел N.15.5.1, Захваченные внешние переменные , в спецификации C # 5.0).

Обратите внимание, что спецификация C # говорит только об анонимных методах, преобразуемых в «деревья выражений», а не в «анонимные классы». Хотя дерево выражений может быть представлено как дополнительные классы C #, например, в компиляторе Microsoft, эта реализация не требуется (как указано в разделе M.5.3 в спецификации C # 5.0). Следовательно, не определено, является ли анонимная функция статической или нет. Более того, в разделе K.6 многое остается открытым в отношении деталей деревьев выражений.

Питер О.
источник
2
+1 на такое поведение, скорее всего, не стоит полагаться по указанным причинам; это во многом деталь реализации.
Lukazoid 03
18

В Roslyn изменено поведение кэширования делегатов. Ранее, как указывалось, любое лямбда-выражение, которое не фиксировало переменные, компилировалось в 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);
        }
    }
}

Изменения в поведении кэширования делегатов в Roslyn объясняют, почему это изменение было внесено.

Юваль Ицчаков
источник
2
Спасибо, мне было интересно, почему мой метод Func <int> f = () => 5 не статичен
vc 74
2

Начиная с C # 6, это всегда будет по умолчанию для методов экземпляра и никогда не будет статическим (поэтому actionMethod.Method.IsStaticвсегда будет ложным).

См. Здесь: Почему лямбда без захвата изменилась со статической в ​​C # 5 на метод экземпляра в C # 6?

и здесь: Разница в оценке статического лямбда-выражения компилятора CSC и Roslyn?

Джеймс Уилкинс
источник
1

Этот метод не имеет закрытий, а также ссылается на сам статический метод (Console.WriteLine), поэтому я ожидал, что он будет статическим. Метод объявит закрывающий анонимный тип для закрытия, но в данном случае это не требуется.

Мел Падден
источник