Dado el siguiente código:
public class C { public void M() { var x = 5; Action<int> action = y => Console.WriteLine(y); } }Usando VS2013, .NET 4.5. Al mirar el código descompilado, podemos ver que el compilador está almacenando en caché al delegado en el sitio de la llamada:
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); } }Mirando el mismo código descompilado en Roslyn (usando TryRoslyn ), produce el siguiente resultado:
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); } } } Ahora podemos ver que el delegado ahora se eleva a una clase privada dentro de C , un comportamiento similar al que estamos acostumbrados a ver al cerrar sobre una variable de instancia/campo (cierre).
Sé que este es un detalle de implementación que puede estar sujeto a cambios en cualquier momento.
Todavía me pregunto, ¿cuáles son los beneficios de llevar al delegado a una nueva clase y almacenarlo en caché allí en lugar de simplemente almacenarlo en caché en el sitio de la llamada?
Editar:
Este problema habla sobre el mismo comportamiento que se preguntó aquí.
Sí. La parte más importante es que el método que contiene la implementación de lambda ahora es un método de instancia.
Puede ver a un delegado como un intermediario que recibe una llamada de instancia a través de Invoke y envía esa llamada de acuerdo con la convención de llamadas del método de implementación.
Tenga en cuenta que existen requisitos ABI de la plataforma que especifican cómo se pasan los argumentos, cómo se devuelven los resultados, qué argumentos se pasan a través de registros y en cuáles, cómo se pasa "esto", etc. La violación de estas reglas puede tener un impacto negativo en las herramientas que se basan en el recorrido de la pila, como los depuradores.
Ahora, si el método de implementación es un método de instancia, lo único que debe suceder dentro del delegado es parchear "esto", que es la instancia delegada en el momento de Invocar, para que sea el objeto de destino adjunto. En ese momento, dado que todo lo demás ya está donde debe estar, el delegado puede saltar directamente al cuerpo del método de implementación. En muchos casos, esto es notablemente menos trabajo de lo que debería suceder si el método de implementación fuera un método estático.
Todavía me pregunto, ¿cuáles son los beneficios de llevar al delegado a una nueva clase y almacenarlo en caché allí en lugar de simplemente almacenarlo en caché en el sitio de la llamada?
Te has perdido otro detalle realmente importante: ahora es un método de instancia. Creo que esa es la clave aquí. IIRC, se descubrió que invocar a un delegado que estaba "respaldado" por un método de instancia era más rápido que invocar a un delegado respaldado por un método estático, que es la motivación detrás del cambio.
Todo esto son rumores, vagamente recordados de pasar tiempo con Dustin Campbell y Kevin Pilch-Bisson (ambos del equipo de Roslyn) en CodeMash, pero tendría sentido dado el código que ha mostrado.
(No he validado la diferencia de rendimiento por mí mismo, y parece que está al revés... pero las partes internas de CLR pueden ser así de divertidas...)