Ayer abrí nuestra solución en Visual Studio 2015 y algunas de nuestras pruebas unitarias (que funcionaron bien en Visual Studio 2013) comenzaron a fallar. Profundice más y descubrí que se debía a que llamar a GetTypes() en un ensamblado arrojaba resultados diferentes. He podido crear un caso de prueba muy simple para ilustrarlo.
Tanto en Visual Studio 2013 como en 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 ejecuto Visual Studio 2013, obtengo el siguiente resultado (como se esperaba).
VS2013Ejemplo.Programa
Cuando ejecuto Visual Studio 2015, obtengo el siguiente resultado (no como se esperaba).
VS2015Ejemplo.Programa
VS2015Ejemplo.Programa+<>c
Entonces, ¿qué es ese VS2015Example.Program+<>c ? Resulta que es la lambda dentro del método .Where() . Sí, así es, de alguna manera esa lambda local está siendo expuesta como un tipo. Si comento el .Where() en VS2015, ya no obtengo esa segunda línea.
Usé Beyond Compare para comparar los dos archivos .csproj, pero las únicas diferencias son el número de versión de 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 uno no lo hizo.
¿Alguien más ha visto esto?
¿Alguien tiene una explicación de por qué una variable local estaría expuesta como un tipo en el nivel de ensamblaje?
¿Alguien más ha visto esto?
Sí, esto se debe al 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 de compilación tuviera que hacer algunos cambios 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 de visualización, donde el delegado se expone como un método de instancia en la clase de visualización, sin tener en cuenta si captura alguna variable local.
Si descompila su método en Roslyn, verá 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á el antiguo compilador? 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 de almacenamiento en caché del delegado en Roslyn