Cambios en el comportamiento del almacenamiento en caché de delegados en Roslyn


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é el 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 ), se obtiene la siguiente salida:

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 cuando cerramos una instancia variable / campo (cierre).

que este es un detalle de implementación que puede estar sujeto a cambios en cualquier momento dado.

Todavía me pregunto, ¿cuáles son los beneficios de levantar el delegado en una nueva clase y almacenarlo en caché allí sobre simplemente almacenarlo en caché en el sitio de la llamada?

Editar:

Este número habla del mismo comportamiento que se ha pedido aquí.

Author: Yuval Itzchakov, 2015-06-17

2 answers

Sí. La parte más importante es que el método que contiene la implementación de lambda es ahora 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 llamada del método implementador.

Tenga en cuenta que hay requisitos ABI de 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 está pasado y así sucesivamente. La violación de estas reglas puede tener un impacto negativo en las herramientas que dependen de stack-walking, 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 "this", que es la instancia delegada en el momento de Invocar, para que sea el objeto de destino incluido. 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 tendría que suceder si el método de implementación fuera un método estático.

 25
Author: VSadov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-17 20:22:18

Todavía me pregunto, ¿cuáles son los beneficios de levantar el delegado en 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 realmente detalle importante - ahora es un método de instancia. Creo que esa es la clave. IIRC, se encontró que invocar un delegado que estaba "respaldado" por un método de instancia era más rápido que invocar 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 Roslyn) en CodeMash, pero tendría sentido dado el código que has mostrado.

(No he validado la diferencia de rendimiento por mí mismo, y suena como si estuviera al revés... pero los internos de CLR pueden ser graciosos así...)

 14
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-17 16:50:52