Разрешить тип из имени класса в другой сборке

87

У меня есть метод, в котором мне нужно разрешить тип класса. Этот класс существует в другой сборке с пространством имен, аналогичным:

MyProject.Domain.Model

Я пытаюсь выполнить следующее:

Type.GetType("MyProject.Domain.Model." + myClassName);

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

Я уверен, что есть гораздо лучший способ выполнить эту задачу, но у меня не было большого опыта в разрешении сборок и перемещении пространств имен внутри для определения типа класса, который я ищу. Какие-нибудь советы или подсказки, чтобы выполнить эту задачу более элегантно?

Брэндон
источник

Ответы:

171

Вам нужно будет добавить имя сборки следующим образом:

Type.GetType("MyProject.Domain.Model." + myClassName + ", AssemblyName");

Чтобы избежать двусмысленности или если сборка находится в GAC, вы должны указать полное имя сборки, например:

Type.GetType("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
Шандор Дриэнхёйзен
источник
Отлично, я знал, что мне не хватает чего-то незначительного, например, сборки. Это решение сработало для моих нужд. Спасибо.
Брэндон
10
И для тех, кто занимается сериализацией: чтобы получить имя с указанием
Майкл Уайлд,
1
Если тип - List <T>, где T - настраиваемый класс, как вы указываете 2 сборки? Т.е. сборка mscorlib для System.Collections.Generic.List и библиотека, содержащая T?
Саймон Грин
@SimonGreen: Вам, вероятно, придется построить его, используя listType.MakeGenericType(itemType). Обе переменные типа могут быть созданы, используя Type.GetType()как в моем ответе.
Sandor Drieënhuizen
object.Assembly.ToString () Может также использоваться для получения полной сборки.
zezba9000
6

Это универсальное решение для людей , которым необходимо загрузить общие типы из динамических внешних ссылок по AssemblyQualifiedName, не зная , из которого сборки все части общего типа исходя из:

    public static Type ReconstructType(string assemblyQualifiedName, bool throwOnError = true, params Assembly[] referencedAssemblies)
    {
        foreach (Assembly asm in referencedAssemblies)
        {
            var fullNameWithoutAssemblyName = assemblyQualifiedName.Replace($", {asm.FullName}", "");
            var type = asm.GetType(fullNameWithoutAssemblyName, throwOnError: false);
            if (type != null) return type;
        }

        if (assemblyQualifiedName.Contains("[["))
        {
            Type type = ConstructGenericType(assemblyQualifiedName, throwOnError);
            if (type != null)
                return type;
        }
        else
        {
            Type type = Type.GetType(assemblyQualifiedName, false);
            if (type != null)
                return type;
        }

        if (throwOnError)
            throw new Exception($"The type \"{assemblyQualifiedName}\" cannot be found in referenced assemblies.");
        else
            return null;
    }

    private static Type ConstructGenericType(string assemblyQualifiedName, bool throwOnError = true)
    {
        Regex regex = new Regex(@"^(?<name>\w+(\.\w+)*)`(?<count>\d)\[(?<subtypes>\[.*\])\](, (?<assembly>\w+(\.\w+)*)[\w\s,=\.]+)$?", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
        Match match = regex.Match(assemblyQualifiedName);
        if (!match.Success)
            if (!throwOnError) return null;
            else throw new Exception($"Unable to parse the type's assembly qualified name: {assemblyQualifiedName}");

        string typeName = match.Groups["name"].Value;
        int n = int.Parse(match.Groups["count"].Value);
        string asmName = match.Groups["assembly"].Value;
        string subtypes = match.Groups["subtypes"].Value;

        typeName = typeName + $"`{n}";
        Type genericType = ReconstructType(typeName, throwOnError);
        if (genericType == null) return null;

        List<string> typeNames = new List<string>();
        int ofs = 0;
        while (ofs < subtypes.Length && subtypes[ofs] == '[')
        {
            int end = ofs, level = 0;
            do
            {
                switch (subtypes[end++])
                {
                    case '[': level++; break;
                    case ']': level--; break;
                }
            } while (level > 0 && end < subtypes.Length);

            if (level == 0)
            {
                typeNames.Add(subtypes.Substring(ofs + 1, end - ofs - 2));
                if (end < subtypes.Length && subtypes[end] == ',')
                    end++;
            }

            ofs = end;
            n--;  // just for checking the count
        }

        if (n != 0)
            // This shouldn't ever happen!
            throw new Exception("Generic type argument count mismatch! Type name: " + assemblyQualifiedName);  

        Type[] types = new Type[typeNames.Count];
        for (int i = 0; i < types.Length; i++)
        {
            try
            {
                types[i] = ReconstructType(typeNames[i], throwOnError);
                if (types[i] == null)  // if throwOnError, should not reach this point if couldn't create the type
                    return null;
            }
            catch (Exception ex)
            {
                throw new Exception($"Unable to reconstruct generic type. Failed on creating the type argument {(i + 1)}: {typeNames[i]}. Error message: {ex.Message}");
            }
        }

        Type resultType = genericType.MakeGenericType(types);
        return resultType;
    }

И вы можете проверить это с помощью этого кода (консольное приложение):

    static void Main(string[] args)
    {
        Type t1 = typeof(Task<Dictionary<int, Dictionary<string, int?>>>);
        string name = t1.AssemblyQualifiedName;
        Console.WriteLine("Type: " + name);
        // Result: System.Threading.Tasks.Task`1[[System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
        Type t2 = ReconstructType(name);
        bool ok = t1 == t2;
        Console.WriteLine("\r\nLocal type test OK: " + ok);

        Assembly asmRef = Assembly.ReflectionOnlyLoad("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
        // Task<DialogResult> in refTypeTest below:
        string refTypeTest = "System.Threading.Tasks.Task`1[[System.Windows.Forms.DialogResult, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
        Type t3 = ReconstructType(refTypeTest, true, asmRef);
        Console.WriteLine("External type test OK: " + (t3.AssemblyQualifiedName == refTypeTest));

        // Getting an external non-generic type directly from references:
        Type t4 = ReconstructType("System.Windows.Forms.DialogResult, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", true, asmRef);

        Console.ReadLine();
    }

Я делюсь своим решением, чтобы помочь людям с той же проблемой, что и я (десериализовать ЛЮБОЙ тип из строки, которая может быть определена как частично, так и целиком во внешней сборке, и ссылки динамически добавляются пользователем приложения).

Надеюсь, это поможет кому-нибудь!

PW
источник
2

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

Применение:

var mytype = TypeConverter<ICommand>.FromString("CreateCustomer");

Код:

    public class TypeConverter<BaseType>
        {
            private static Dictionary<string, Type> _types;
            private static object _lock = new object();

            public static Type FromString(string typeName)
            {
                if (_types == null) CacheTypes();

                if (_types.ContainsKey(typeName))
                {
                    return _types[typeName];
                }
                else
                {
                    return null;
                }
            }

            private static void CacheTypes()
            {
                lock (_lock)
                {
                    if (_types == null)
                    {
                        // Initialize the myTypes list.
                        var baseType = typeof(BaseType);
                        var typeAssembly = baseType.Assembly;
                        var types = typeAssembly.GetTypes().Where(t => 
                            t.IsClass && 
                            !t.IsAbstract && 
                            baseType.IsAssignableFrom(t));

                        _types = types.ToDictionary(t => t.Name);
                    }
                }
            }
        }

Очевидно, вы можете настроить метод CacheTypes для проверки всех сборок в AppDomain или другой логики, которая лучше подходит для вашего варианта использования. Если ваш вариант использования позволяет загружать типы из нескольких пространств имен, вы можете изменить ключ словаря, чтобы FullNameвместо этого использовать тип . Или, если ваши типы не наследуются от общего интерфейса или базового класса, вы можете удалить <BaseType>и изменить метод CacheTypes, чтобы использовать что-то вроде.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")

EverPresent
источник
1

Сначала загрузите сборку, а затем тип. пример: Assembly DLL = Assembly.LoadFile (PATH); DLL.GetType (имя_типа);

azulay7
источник
0

Можете ли вы использовать любой из стандартных способов?

typeof( MyClass );

MyClass c = new MyClass();
c.GetType();

Если нет, вам нужно будет добавить информацию о сборке в Type.GetType.

Джерод Хоутеллинг
источник
0

Краткий и динамичный подход с использованием AssemblyQualifiedNameсобственности -

Type.GetType(Type.GetType("MyProject.Domain.Model." + myClassName).AssemblyQualifiedName)

Наслаждайтесь!

Simonbor
источник
10
Если Type.GetType ("MyProject.Domain.Model." + MyClassName) терпит неудачу, как можно предотвратить это, заключив его в другой вызов GetType?
Кейн
1
В любом случае вы можете заключить его в блок try catch с помощью System.NullReferenceException. Скорее всего, вы ошибетесь в этом - «MyProject.Domain.Model.ClassName, ClassName, Version = 2.0.0.0, Culture = нейтральный, PublicKeyToken = b77a5c561934e089», тогда в этом - «MyProject.Domain.Model.» ...
simonbor
1
@Kaine Я не уверен, что имел в виду simonbor, но если вы используете GetType (). AssemblyQualifiedName при ЗАПИСИ строки, тогда вам не нужно беспокоиться об этом при использовании строки для преобразования в тип.
Серхио Поррес