Подтвердить что ты не робот

Поведение Assembly.GetTypes() изменено в 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 + < > с

Итак, что это за тип VS2015Example.Program+<>c? Оказывает это лямбда внутри метода .Where(). Да, это правильно, так или иначе, что местная лямбда подвергается как тип. Если я прокомментирую .Where() в VS2015, то я больше не получаю эту вторую строку.

Я использовал Beyond Compare для сравнения двух файлов .csproj, но единственными отличиями являются номер версии VS, GUID проекта, имена пространства имен по умолчанию и сборка, а VS2015 имеет ссылку на System.Net. Http, что VS2013 не сделал.

Кто-нибудь еще видел это?

Есть ли у кого-нибудь объяснение, почему локальная переменная будет отображаться как тип на уровне сборки?

4b9b3361

Ответ 1

Кто-нибудь еще видел это?

Да, это вызвано новым поведением компилятора для подъема лямбда-выражений.

Раньше, если лямбда-выражение не фиксировало каких-либо локальных переменных, оно было бы кэшировано как статический метод на сайте вызова, что заставило команду компилятора перескакивать некоторые обручи, чтобы правильно выровнять аргументы метода и this. Новое поведение в Roslyn заключается в том, что все лямбда-выражения поднимаются в класс отображения, где делегат отображается как метод экземпляра в классе отображения, не обращая внимания на то, фиксирует ли он какие-либо локальные переменные.

Если вы декомпилируете свой метод в 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);

Подробнее см. мой вопрос Делегировать изменения поведения кэширования в Roslyn