C# Visual Studio 2015中Assembly.GetTypes()的行为已更改

C# Visual Studio 2015中Assembly.GetTypes()的行为已更改,c#,visual-studio-2013,compiler-construction,visual-studio-2015,roslyn,C#,Visual Studio 2013,Compiler Construction,Visual Studio 2015,Roslyn,昨天我在Visual Studio 2015中打开了我们的解决方案,我们的一些单元测试(在Visual Studio 2013中运行良好)开始失败。Digger Deep我发现这是因为对程序集调用GetTypes()返回的结果不同。我已经能够创建一个非常简单的测试用例来说明它 在Visual Studio 2013和2015中,我使用.NET Framework 4.5.2创建了一个新的控制台应用程序。我在两个项目中都添加了以下代码 class Program { static void

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

VS2013示例程序

当我在Visual Studio 2015中运行时,我得到了以下输出(不是预期的)

VS2015示例程序

VS2015示例程序+c

那么那
VS2015Example.Program+c
类型是什么呢?原来是
.Where()
方法中的lambda。是的,没错,不知何故,当地的lambda被曝光为一种类型。如果我在VS2015中注释掉
.Where()
,那么我就不再得到第二行了

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

还有谁见过这个吗

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

还有谁见过这个吗

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

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

如果在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;
}

有关更多信息,请参见我的问题

谢谢您提供的信息。看起来有点吓人,因为这感觉像是一个改变,可能会导致很多工作正常的现有代码突然出现bug。这些年来,我已经记不清我编写代码枚举程序集中类型的次数了。感觉好像
GetTypes()
应该有一个重载,让开发人员显式声明他们是否希望包含编译器生成的类型。@CraigW。为此编写一个扩展方法应该很容易,但我完全同意这是一个潜在的突破性变化,因为即使使用扩展方法,它在默认情况下也不会被调用,也许你应该向github上的Roslyn团队提交一个问题?@Craig这不是一个突破性变化,这是一个实现细节。如果您在代理中捕获了一个变量,您将看到相同的行为。@YuvalItzchakov:我们必须就是否是破坏性更改达成一致意见。它打破了我的密码。:-)@编译器神奇地公开了以前没有的新类型,这不仅仅是一个简单的实现细节。那不是真的。编译器现在在调用站点生成一个显示类而不是静态方法,这是一个实现细节。如果您的代码对某个局部值有任何影响,那么您可能会在前面编写代码时看到此错误。使用
Assembly.GetTypes
时,您确实希望看到程序集中的所有类,即使它们是由编译器生成的。
var types = typeof(Program)
            .Assembly
            .GetTypes()
            .Where(t => !t.IsAbstract && 
                         t.IsClass && 
                         Attribute.GetCustomAttribute(
                            t, typeof (CompilerGeneratedAttribute)) == null);