Assembly.GetTypes() 的行为在 Visual Studio 2015 年发生了变化

Behavior of Assembly.GetTypes() changed in Visual Studio 2015

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

在 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+<>c

那么 VS2015Example.Program+<>c 类型是什么?结果是 .Where() 方法中的 lambda。是的,没错,本地 lambda 以某种方式作为一种类型公开。如果我在 VS2015 中注释掉 .Where() 那么我就不会再看到第二行了。

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

还有其他人看过吗?

有没有人解释为什么局部变量会在程序集级别公开为类型?

Has anyone else seen this?

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

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

如果您在 Roslyn 中反编译您的方法,您会看到:

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 属性的 class 来获得所需的结果:

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

更多请看我的问题