Resolva o tipo a partir do nome da classe em uma montagem diferente

87

Eu tenho um método em que preciso resolver o tipo de uma classe. Esta classe existe em outro assembly com o namespace semelhante a:

MyProject.Domain.Model

Estou tentando realizar o seguinte:

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

Isso funciona muito bem se o código que está executando essa ação estiver no mesmo assembly da classe cujo tipo estou tentando resolver; no entanto, se minha classe estiver em um assembly diferente, esse código falhará.

Tenho certeza de que existe uma maneira muito melhor de realizar essa tarefa, mas não tenho muita experiência em resolver assemblies e percorrer namespaces internos para resolver o tipo de classe que estou procurando. Algum conselho ou dica para realizar essa tarefa com mais elegância?

Brandon
fonte

Respostas:

171

Você terá que adicionar o nome da montagem assim:

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

Para evitar ambigüidade ou se o assembly estiver localizado no GAC, você deve fornecer um nome de assembly totalmente qualificado como:

Type.GetType("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
Sandor Drieënhuizen
fonte
Excelente, eu sabia que estava faltando algo menor, como incluir a montagem. Essa solução funcionou para minhas necessidades. Obrigado.
Brandon de
10
E para aqueles que lidam com serialização: Para obter o nome qualificado do assembly, há a propriedade Type.AssemblyQualifiedName
Michael Wild
1
Se o tipo for uma List <T>, onde T é uma classe personalizada, como você especifica 2 assemblies? Ou seja, o assembly mscorlib para System.Collections.Generic.List e a biblioteca que contém T?
Simon Green
@SimonGreen: Você provavelmente terá que construir usando listType.MakeGenericType(itemType). Ambas as variáveis ​​de tipo podem ser construídas usando Type.GetType()like em minha resposta.
Sandor Drieënhuizen
object.Assembly.ToString () Pode ser usado também para obter o assembly completo.
zezba9000
5

Esta solução universal é para pessoas que precisam carregar tipos genéricos de referências externas dinâmicas por AssemblyQualifiedName, sem saber de qual assembly são todas as partes do tipo genérico vindo:

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

E você pode testá-lo com este código (aplicativo de console):

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

Estou compartilhando minha solução para ajudar pessoas com o mesmo problema que eu (para desserializar QUALQUER tipo de string que poderia ser definido parcialmente ou como um todo em um assembly referenciado externamente - e as referências são adicionadas dinamicamente pelo usuário do aplicativo).

Espero que ajude alguém!

PW
fonte
2

Semelhante ao OP, precisei carregar um subconjunto limitado de tipos por nome (no meu caso, todas as classes estavam em um único assembly e implementaram a mesma interface). Tive muitos problemas estranhos ao tentar usarType.GetType(string) em um assembly diferente (mesmo adicionando o AssemblyQualifiedName conforme mencionado em outros posts). Aqui está como resolvi o problema:

Uso:

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

Código:

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

Obviamente, você pode ajustar o método CacheTypes para inspecionar todos os assemblies no AppDomain ou outra lógica que melhor se adapte ao seu caso de uso. Se o seu caso de uso permite que os tipos sejam carregados de vários namespaces, você pode querer alterar a chave do dicionário para usar o tipo FullName. Ou se seus tipos não herdam de uma interface comum ou classe base, você pode remover o <BaseType>método CacheTypes e alterar para usar algo como.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")

EverPresent
fonte
1

Primeiro carregue a montagem e depois o tipo. ex: DLL de montagem = Assembly.LoadFile (PATH); DLL.GetType (typeName);

azulay7
fonte
0

Você pode usar qualquer uma das formas padrão?

typeof( MyClass );

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

Caso contrário, você terá que adicionar informações ao Type.GetType sobre o assembly.

Jerod Houghtelling
fonte
0

Abordagem curta e dinâmica usando AssemblyQualifiedNamepropriedade -

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

Aproveitar!

Simonbor
fonte
10
Se Type.GetType ("MyProject.Domain.Model." + MyClassName) falhar, como envolvê-lo em outra chamada GetType pode impedir isso?
Kaine
1
Em qualquer caso, você pode envolvê-lo em um bloco try catch com uma System.NullReferenceException. É muito mais provável que se engane neste - "MyProject.Domain.Model.ClassName, ClassName, Version = 2.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089" do que neste - "MyProject.Domain.Model." ...
simonbor
1
@Kaine Não tenho certeza do que simonbor quis dizer, mas se você usar GetType (). AssemblyQualifiedName ao ESCREVER a string, não precisará se preocupar com isso ao usar a string para resolver para um tipo.
Sergio Porres