【问题标题】:Behavior of Assembly.GetTypes() changed in Visual Studio 2015在 Visual Studio 2015 中更改了 Assembly.GetTypes() 的行为
【发布时间】:2015-07-21 14:34:43
【问题描述】:

我昨天在 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。 VS2013 没有的那个http。

还有其他人看过吗?

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

【问题讨论】:

    标签: c# visual-studio-2013 compiler-construction visual-studio-2015 roslyn


    【解决方案1】:

    还有其他人看过吗?

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

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

    如果你在 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

    【讨论】:

    • 感谢您的信息。看起来有点吓人,因为这感觉就像是一个更改,可能会导致许多运行良好的现有代码突然出现错误。多年来,我已经记不清我编写枚举程序集中类型的代码的次数了。感觉GetTypes() 应该有一个重载,让开发人员明确说明他们是否想要包含编译器生成的类型。
    • @CraigW。为此编写扩展方法应该很容易,但我完全同意这是一个潜在的重大更改,因为即使使用扩展方法,默认情况下也不会调用它,也许你应该在 github 上向 Roslyn 团队提出问题?跨度>
    • @Craig 这不是重大更改,而是实现细节。如果您在委托中捕获了一个变量,您会看到相同的行为。
    • @YuvalItzchakov:我们必须同意不同意这是否是一个重大变化。它破坏了我的代码。 :-)
    • @CraigW 编译器神奇地公开以前不存在的新类型不仅仅是一个简单的实现细节。这不是真的。编译器现在在调用站点生成一个显示类而不是静态方法这一事实是一个实现细节。如果您的代码在某些本地值上有任何 clojure,那么您在仅编写代码时就会更早地看到这个错误。当您使用Assembly.GetTypes 时,您确实期望查看程序集中的所有类,即使它们是编译器生成的。
    猜你喜欢
    • 1970-01-01
    • 2018-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-07
    • 2016-12-14
    相关资源
    最近更新 更多