Comportamiento del Montaje.GetTypes () cambiado en Visual Studio 2015


Abrí nuestra solución en Visual Studio 2015 ayer y algunas de nuestras pruebas unitarias (que funcionaron bien en Visual Studio 2013) comenzaron a fallar. Digger deeper Descubrí que era porque llamar GetTypes() en un ensamblaje estaba devolviendo resultados diferentes. He sido capaz de crear un caso de prueba muy simple para ilustrarlo.

En Visual Studio 2013 y 2015 creé una nueva aplicación de consola usando.NET Framework 4.5.2. Puse el siguiente código en ambos proyectos.

class Program
{
    static void Main(string[] args)
    {
        var types = typeof(Program).Assembly.GetTypes()
                .Where(t => !t.IsAbstract && t.IsClass);

        foreach (var type in types)
        {
            Console.WriteLine(type.FullName);
        }

        Console.ReadKey();
    }
}

Cuando ejecutar en Visual Studio 2013 obtengo la siguiente salida (como se esperaba).

VS2013Ejemplo.Programa

Cuando corro en Visual Studio 2015 obtengo la siguiente salida (no como se esperaba).

VS2015Example.Programa

VS2015Example.Programa + c

Entonces, ¿qué es ese tipo VS2015Example.Program+<>c? Resulta que es la lambda dentro del método .Where(). Sí, es cierto, de alguna manera ese lambda local está siendo expuesto como un tipo. Si comento el .Where() en VS2015 entonces ya no entiendo esa segunda línea.

He usado Beyond Compare para comparar los dos .los archivos csproj, pero las únicas diferencias son el número de versión VS, el GUID del proyecto, los nombres del espacio de nombres predeterminado y el ensamblado, y el VS2015 tenía una referencia a System.Net.Http que el VS2013 no tenía.

¿Alguien más ha visto esto?

¿Alguien tiene una explicación de por qué una variable local estaría siendo expuesta como un tipo a nivel de ensamblado?

Author: Yuval Itzchakov, 2015-07-21

1 answers

¿Alguien más ha visto esto?

Sí, esto es causado por el nuevo comportamiento del compilador para levantar expresiones lambda.

Anteriormente, si una expresión lambda no capturaba ninguna variable local, se almacenaba en caché como un método estático en el sitio de la llamada, lo que hacía que el equipo del compilador necesitara saltar algunos aros para alinear correctamente los argumentos del método y el parámetro this. El nuevo comportamiento en Roslyn es que todas las expresiones lambda se elevan a una clase display, donde el delegado se expone como un método de instancia en la clase display, sin tener en cuenta si captura alguna variable local.

Si descompilas tu método en Roslyn, verás esto:

private static void Main(string[] args)
{
    IEnumerable<Type> arg_33_0 = typeof(Program).Assembly.GetTypes();
    Func<Type, bool> arg_33_1;
    if (arg_33_1 = Program.<>c.<>9__0_0 == null)
    {
        arg_33_1 = Program.<>c.<>9__0_0 = 
                        new Func<Type, bool>(Program.<>c.<>9.<Main>b__0_0);
    }
    using (IEnumerator<Type> enumerator = arg_33_0.Where(arg_33_1).GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            Console.WriteLine(enumerator.Current.FullName);
        }
    }
    Console.ReadKey();
}

[CompilerGenerated]
[Serializable]
private sealed class <>c
{
    public static readonly Program.<>c <>9;
    public static Func<Type, bool> <>9__0_0;
    static <>c()
    {
        // Note: this type is marked as 'beforefieldinit'.
        Program.<>c.<>9 = new Program.<>c();
    }
    internal bool <Main>b__0_0(Type t)
    {
        return !t.IsAbstract && t.IsClass;
    }
}

¿Dónde está con el compilador antiguo, verías esto:

[CompilerGenerated]
private static Func<Type, bool> CS$<>9__CachedAnonymousMethodDelegate1;

private static void Main(string[] args)
{
    IEnumerable<Type> arg_34_0 = typeof(Program).Assembly.GetTypes();
    if (Program.CS$<>9__CachedAnonymousMethodDelegate1 == null)
    {
        Program.CS$<>9__CachedAnonymousMethodDelegate1 = 
                            new Func<Type, bool>(Program.<Main>b__0);
    }
    IEnumerable<Type> types =
                arg_34_0.Where(Program.CS$<>9__CachedAnonymousMethodDelegate1);

    foreach (Type type in types)
    {
        Console.WriteLine(type.FullName);
    }
    Console.ReadKey();
}

[CompilerGenerated]
private static bool <Main>b__0(Type t)
{
    return !t.IsAbstract && t.IsClass;
}

Puede obtener el resultado deseado filtrando las clases que tienen el atributo CompilerGenerated adjunto:

var types = typeof(Program)
            .Assembly
            .GetTypes()
            .Where(t => !t.IsAbstract && 
                         t.IsClass && 
                         Attribute.GetCustomAttribute(
                            t, typeof (CompilerGeneratedAttribute)) == null);

Para obtener más información, consulte mi pregunta Cambios en el comportamiento del almacenamiento en caché de delegados en Roslyn

 29
Author: Yuval Itzchakov,
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
2017-05-23 11:46:07