我昨天在Visual Studio 2015中打开了我们的解决方案,并且一些单元测试(在Visual Studio 2013中运行良好)开始失败。 Digger更深入地发现,这是因为在程序集上调用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中运行时,得到以下输出(按预期)。

在Visual Studio 2015中运行时,得到以下输出(与预期不符)。

VS2015Example.Program+<>c是什么类型?原来是.Where()方法中的lambda。是的,没错,以某种方式将本地lambda公开为一种类型。如果我在VS2015中注释掉.Where(),那么我将不再获得第二行。
我已经使用Beyond Compare比较了两个.csproj文件,但唯一的区别是VS版本号,项目GUID,默认 namespace 和程序集的名称,而VS2015引用了System.Net.Http, VS2013没有。
还有其他人看到吗?
是否有人解释为什么局部变量会在程序集级别公开为类型?

最佳答案



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

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

如果在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属性的类来获得所需的结果:
var types = typeof(Program)
            .Assembly
            .GetTypes()
            .Where(t => !t.IsAbstract &&
                         t.IsClass &&
                         Attribute.GetCustomAttribute(
                            t, typeof (CompilerGeneratedAttribute)) == null);

有关更多信息,请参阅我的问题Delegate caching behavior changes in Roslyn

10-05 23:47