24

我昨天在Visual Studio 2015中打开了我们的解决方案,我们的一些单元测试(在Visual Studio 2013中运行正常)开始失败。深挖掘机我发现这是因为在程序集上调用GetTypes()返回不同的结果。我已经能够创建一个非常简单的测试案例来说明它。Assembly.GetTypes()在Visual Studio 2015中的行为已更改

在Visual Studio 2013和2015中,我使用.NET Framework 4.5.2创建了一个新的控制台应用程序。我把这两个项目中的代码。

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(); 
    } 
} 

当我在Visual Studio 2013中运行时,我得到以下输出(如预期的那样)。

VS2013Example.Program

当我运行在Visual Studio 2015年我得到下面的输出(未如预期)。

VS2015Example.Program

VS2015Example.Program + <>ç

那么,什么是VS2015Example.Program+<>c类型?原来,这是.Where()方法中的lambda。是的,这是正确的,不知何故,本地lambda被暴露为类型。如果我在VS2015中注释掉.Where(),那么我不会再获得第二条线。

我已经使用Beyond Compare来比较两个.csproj文件,但唯一的区别是VS版本号,项目GUID,默认命名空间和程序集的名称,以及VS2015有一个对System.Net的引用.Http VS2013没有。

有没有其他人看到过这个?

有没有人有解释为什么一个局部变量会在汇编级别作为类型暴露?

回答

29

有没有其他人看到过这个?

是的,这是由提升lambda表达式的新编译器行为引起的。

以前,如果一个lambda表达式没有捕获任何局部变量,它将作为静态方法缓存在调用站点,这使编译器团队需要跳过一些环节,以便正确地对齐方法参数和this参数。 Roslyn中的新行为是,所有的lambda表达式都被提升到一个显示类中,其中代表作为显示类中的一个实例方法公开,不管它是否捕获任何局部变量。

如果您在罗斯林反编译你的方法,你会看到这一点:

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; 
    } 
} 

凡与老编译器,你会看到这一点:

[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; 
} 

您可以通过过滤得到期望的结果有CompilerGenerated属性附加到他们:

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

For更多,看我的问题Delegate caching behavior changes in Roslyn

+1

感谢您的信息。看起来有点可怕,因为它感觉像一个变化,可能会导致很多现有的代码工作得很好,突然出现错误。多年来,我已经忘记了我编写代码的次数,这些代码列举了程序集中的类型。感觉像'GetTypes()'应该可能有一个重载,让开发人员明确地声明他们是否希望包含编译器生成的类型。 –

+0

@CraigW。应该很容易编写一个扩展方法,但我完全同意这是一个潜在的重大变化,因为即使使用扩展方法,它不会默认调用,也许你应该在github上提交Roslyn团队的问题? –

+0

@克雷格这不是一个突破性的变化,那是一个***实现细节***。如果你在委托中捕获了一个变量,你会看到相同的行为。 –