Почему System.Type.GetType( "Xyz" ) возвращает null, если существует типof (Xyz)?

Я столкнулся с странным поведением в моем (огромном) проекте .NET 4. В какой-то момент кода я имею в виду полностью квалифицированный тип, скажем:

System.Type type = typeof (Foo.Bar.Xyz);

позже, я делаю это:

System.Type type = System.Type.GetType ("Foo.Bar.Xyz");

и я вернусь null. Я не могу понять, почему это происходит, потому что мое имя типа правильно, и я проверил с другими типами, и они правильно решены. Более того, следующий запрос LINQ находит тип:

var types = from assembly in System.AppDomain.CurrentDomain.GetAssemblies ()
            from assemblyType in assembly.GetTypes ()
            where assemblyType.FullName == typeName
            select assemblyType;

System.Type type = types.FirstOrDefault ();

Есть ли причины, по которым System.Type.GetType может выйти из строя?

Мне, наконец, пришлось прибегнуть к этой части кода вместо GetType:

System.Type MyGetType(string typeName)
{
    System.Type type = System.Type.GetType (typeName);

    if (type == null)
    {
        var types = from assembly in System.AppDomain.CurrentDomain.GetAssemblies ()
                    from assemblyType in assembly.GetTypes ()
                    where assemblyType.FullName == typeName
                    select assemblyType;

        type = types.FirstOrDefault ();
    }

    return type;
}

Ответы

Ответ 1

Если вы просто укажете имя класса (которое, конечно же, должно быть полностью определено с точки зрения пространства имен) Type.GetType(string) будет отображаться только в текущей исполняемой сборке и mscorlib. Если вы хотите получать типы из любой другой сборки, вам нужно указать полное полное имя, включая информацию о сборке. Как говорит Франсуа, Type.AssemblyQualifiedName - хороший способ увидеть это. Вот пример:

using System;
using System.Windows.Forms;

class Test
{
    static void Main()
    {
        string name = typeof(Form).AssemblyQualifiedName;
        Console.WriteLine(name);

        Type type = Type.GetType(name);
        Console.WriteLine(type);
    }
}

Вывод:

System.Windows.Forms.Form, System.Windows.Forms, Version = 4.0.0.0, Culture = neutral,
PublicKeyToken = b77a5c561934e089
System.Windows.Forms.Form

Обратите внимание, что если вы используете сильно названную сборку (например, Form в этом случае), вы должны включить всю информацию о сборке - управление версиями, токен открытого ключа и т.д.

Если вы используете сборку с ненавязчивым именем, это проще - что-то вроде:

Foo.Bar.Baz, MyCompany.MyAssembly

для типа, называемого Baz в пространстве имен Foo.Bar, в сборке MyCompany.MyAssembly. Обратите внимание на отсутствие ".dll" в конце - эту часть имени файла, но не имя сборки.

Вы также должны знать различия между именами С# и именами CLR для таких вещей, как вложенные классы и дженерики. Например, typeof(List<>.Enumerator) имеет имя System.Collections.Generic.List`1+Enumerator[T]. Сторона дженериков сложна для разработки, но бит вложенного типа прост - он просто представлен как "+", а не ".". вы должны использовать в С#.

Ответ 2

Насколько я знаю, GetType ищет "Xyz" в сборке с именем Foo.Bar.dll, и я предполагаю, что ее не существует.

GetType полагается на передачу точного пути к Xyz в сборке. Сборка и пространство имен не обязательно должны быть связаны.

Попробуйте System.Type type = System.Type.GetType(typeof(Foo.Bar.Xyz).AssemblyQualifiedName) и посмотрите, работает ли это.

Причина, по которой вы нашли это в примере LINQ, заключается в том, что вы используете GetAssemblies, который получает сборки, которые были загружены в текущий контекст выполнения, и, следовательно, содержит детали, необходимые для поиска всех типов в сборках.

Ответ 3

Из документация MSDN (мой акцент):

Если typeName включает пространство имен, но не имя сборки, этот метод ищет только сборку вызывающего объекта и Mscorlib.dll в этом порядке. Если typeName имеет полную квалификацию с неполным или полным именем сборки, этот метод выполняет поиск в указанной сборке. Если сборка имеет сильное имя, требуется полное имя сборки.

Ответ 4

Я просто наткнулся на подобную проблему и хочу оставить это здесь.

Прежде всего, вы можете указать AssemblyName в строке

var type = System.Type.GetType("Foo.Bar.Xyz, Assembly.Name");

Однако это работает только для сборок без сильного имени. Объяснение уже в ответе Саймонса If the assembly has a strong name, a complete assembly name is required.

Моя проблема заключалась в том, что я должен был разрешить System.Dictionary<?,?> из строки во время выполнения. Для Dictionary<int, string> это может быть легко, но как насчет Dictionary<int, Image>?

это приведет к

var typeName = "System.Collections.Generic.Dictionary`2[System.Int32, [System.Drawing.Image, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]]";

Но я не хочу писать сильное имя. Особенно потому, что я не хочу включать версии, так как планирую нацелить несколько фреймворков на свой код.

Итак, вот мое решение

    privat statice void Main()
    {
        var typeName = "System.Collections.Generic.Dictionary`2[System.Int32, [System.Drawing.Image, System.Drawing]]";
        var type = Type.GetType(typeName, ResolveAssembly, ResolveType);
    }

    private static Assembly ResolveAssembly(AssemblyName assemblyName)
    {
        if (assemblyName.Name.Equals(assemblyName.FullName))
            return Assembly.LoadWithPartialName(assemblyName.Name);
        return Assembly.Load(assemblyName);
    }

    private static Type ResolveType(Assembly assembly, string typeName, bool ignoreCase)
    {
        return assembly != null
            ? assembly.GetType(typeName, false, ignoreCase)
            : Type.GetType(typeName, false, ignoreCase);
    }

Type.GetType(...) имеет перегрузку, которая активирует функцию для сборки и разрешения типа, которая в аккуратном состоянии. Assembly.LoadWithPartialName устарел, но если он упадет в будущем, я мог бы подумать о замене (повторить все сборки в текущем AppDomain и сравнить частичные имена).