Delegar cambios de comportamiento de caché en Roslyn

Dado el siguiente código:

public class C { public void M() { var x = 5; Action action = y => Console.WriteLine(y); } } 

Usando VS2013, .NET 4.5. Al mirar el código descomstackdo, podemos ver que el comstackdor está almacenando en caché al delegado en el sitio de la llamada:

 public class C { [CompilerGenerated] private static Action CS$9__CachedAnonymousMethodDelegate1; public void M() { if (C.CS$9__CachedAnonymousMethodDelegate1 == null) { C.CS$9__CachedAnonymousMethodDelegate1 = new Action(C.b__0); } Action arg_1D_0 = C.CS$9__CachedAnonymousMethodDelegate1; } [CompilerGenerated] private static void b__0(int y) { Console.WriteLine(y); } } 

Al observar el mismo código descomstackdo en Roslyn (usando TryRoslyn ), se obtiene el siguiente resultado:

 public class C { [CompilerGenerated] private sealed class c__DisplayClass0 { public static readonly C.c__DisplayClass0 CS$9__inst; public static Action 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 b__1(int y) { Console.WriteLine(y); } } public void M() { Action arg_22_0; if (arg_22_0 = C.c__DisplayClass0.CS$9__CachedAnonymousMethodDelegate2 == null) { C.c__DisplayClass0.CS$9__CachedAnonymousMethodDelegate2 = new Action(C.c__DisplayClass0.CS$9__inst.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 una variable / campo de instancia (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 elevar al delegado a una nueva clase y almacenarlo allí simplemente guardándolo en caché en el sitio de llamadas?

Editar:

Este problema habla sobre el mismo comportamiento que aquí.

Sí. La parte más importante es que el método que contiene la implementación lambda ahora es un método de instancia.

Puede ver a un delegado como intermediario recibiendo una llamada de instancia a través de Invocar y enviando esa llamada de acuerdo con la convención de llamadas del método de implementación.

Tenga en cuenta que hay requisitos de la plataforma ABI que especifican cómo se pasan los argumentos, cómo se devuelven los resultados, qué argumentos se pasan a través de los registros y en cuáles, cómo se transmite “this” y así sucesivamente. La violación de estas reglas puede tener un impacto negativo en las herramientas que se basan en el astackmiento, 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 parchar “this”, que es la instancia delegada en el momento de Invoke, para que sea el objeto Target incluido. En ese punto, dado que todo lo demás ya está donde debe estar, el delegado puede ir directamente al cuerpo del método de implementación. En muchos casos, esto es notablemente menos trabajo de lo que tendría que suceder si el método de implementación fuera un método estático.

Todavía me pregunto, ¿cuáles son los beneficios de elevar al delegado a una nueva clase y almacenarlo allí simplemente guardándolo en caché en el sitio de llamadas?

Te has perdido otro detalle realmente importante: ahora es un método de instancia. Creo que esa es la clave aquí. IIRC, se encontró 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 es un rumor, vagamente recordado 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 has mostrado.

(No he validado la diferencia de rendimiento para mí, y parece que está al revés … pero las internas de CLR pueden ser divertidas así …)