Почему некоторые выражения лямбда С# компилируются для статических методов?

Как вы можете видеть в приведенном ниже коде, я объявил объект 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();
}

Вывод:

example output of sample

Ответ 1

Это, скорее всего, потому, что нет закрытий, например:

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

Ответ 2

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

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

Ответ 3

Делегированное поведение кеширования было изменено в 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 говорит о том, почему это изменение было сделано.

Ответ 4

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

Ответ 5

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

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

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