Почему некоторые выражения лямбда С# компилируются для статических методов?
Как вы можете видеть в приведенном ниже коде, я объявил объект 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?