metodo - ¿Por qué algunas expresiones lambda de C#se compilan con métodos estáticos?
metodo anonimo c# (5)
A partir de C # 6, esto siempre será predeterminado a los métodos de instancia ahora, y nunca será estático (por lo que
actionMethod.Method.IsStatic
siempre será falso).
Vea aquí: ¿Por qué una lambda sin captura cambió de una estática en C # 5 a un método de instancia en C # 6?
y aquí: ¿ Diferencia en la evaluación de expresión estática lambda del compilador CSC y Roslyn?
Como puede ver en el siguiente código, he declarado un objeto
Action<>
como variable.
¿Alguien podría decirme por qué este delegado de método de acción se comporta como un método estático?
¿Por qué devuelve
true
en el siguiente código?
Código:
public static void Main(string[] args)
{
Action<string> actionMethod = s => { Console.WriteLine("My Name is " + s); };
Console.WriteLine(actionMethod.Method.IsStatic);
Console.Read();
}
Salida:
El "método de acción" es estático solo como un efecto secundario de la implementación. Este es un caso de un método anónimo sin variables capturadas. Como no hay variables capturadas, el método no tiene requisitos adicionales de vida útil más allá de los de las variables locales en general. Si hizo referencia a otras variables locales, su vida útil se extiende a la vida útil de esas otras variables (consulte la sección L.1.7, Variables locales , y la sección N.15.5.1, Variables externas capturadas , en la especificación C # 5.0).
Tenga en cuenta que la especificación C # solo habla de métodos anónimos que se convierten en "árboles de expresión", no en "clases anónimas". Si bien el árbol de expresión podría representarse como clases adicionales de C #, por ejemplo, en el compilador de Microsoft, esta implementación no es necesaria (como se reconoce en la sección M.5.3 en la especificación de C # 5.0). Por lo tanto, no está definido si la función anónima es estática o no. Además, la sección K.6 deja mucho abierto en cuanto a los detalles de los árboles de expresión.
El comportamiento de caché de delegado se modificó en Roslyn.
Anteriormente, como se indicó, cualquier expresión lambda que no capturara variables se compilaba en un método
static
en el sitio de la llamada.
Roslyn cambió este comportamiento.
Ahora, cualquier lambda, que captura variables o no, se transforma en una clase de visualización:
Dado este ejemplo:
public class C
{
public void M()
{
var x = 5;
Action<int> action = y => Console.WriteLine(y);
}
}
Salida del compilador nativo:
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);
}
}
}
Los cambios en el comportamiento de almacenamiento en caché de delegados en Roslyn hablan sobre por qué se realizó este cambio.
El método no tiene cierres y también hace referencia a un método estático en sí mismo (Console.WriteLine), por lo que esperaría que fuera estático. El método declarará un tipo anónimo de cierre para un cierre, pero en este caso no es obligatorio.
Esto es muy probable porque no hay cierres, por ejemplo:
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);
Esto generará
false
para
withClosure
y
true
para
withoutClosure
.
Cuando usa una expresión lambda, el compilador crea una pequeña clase para contener su método, esto se compilaría de forma similar a lo siguiente (la implementación real probablemente varía ligeramente):
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);
}
Puede ver que las instancias resultantes de
Action<string>
realmente apuntan a métodos en estas clases generadas.