Вирішити тип із назви класу в іншому складі


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
А для тих, хто займається серіалізацією: Для отримання назви, яка відповідає збірці, є властивість Type.AssemblyQualifiedName
Michael Wild

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();
    }

Я ділюсь своїм рішенням, щоб допомогти людям з тією ж проблемою, що і я (десеріалізувати БУДЬ-Який тип із рядка, який може бути визначений як частково, так і в цілому в збірці із зовнішніми посиланнями - а посилання динамічно додаються користувачем програми).

Сподіваюся, це комусь допоможе!


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.")


1

Спочатку завантажте збірку, а потім тип. напр .: DLL збірки = Assembly.LoadFile (PATH); DLL.GetType (typeName);


0

Чи можете ви використовувати будь-який із стандартних способів?

typeof( MyClass );

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

Якщо ні, вам доведеться додати інформацію до Type.GetType про збірку.


0

Короткий та динамічний підхід з використанням AssemblyQualifiedNameвластивості -

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

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


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 при ЗАПИСІ рядка, то вам не доведеться турбуватися про це, використовуючи рядок для вирішення типу.
Серхіо Поррес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.