Risolvi il tipo dal nome della classe in un assembly diverso


87

Ho un metodo in cui devo risolvere il tipo di una classe. Questa classe esiste in un altro assembly con lo spazio dei nomi simile a:

MyProject.Domain.Model

Sto tentando di eseguire quanto segue:

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

Funziona benissimo se il codice che esegue questa azione è nello stesso assembly della classe di cui sto cercando di risolvere il tipo, tuttavia, se la mia classe è in un assembly diverso, questo codice non riesce.

Sono sicuro che ci sia un modo molto migliore per eseguire questa operazione, ma non ho avuto molta esperienza con la risoluzione di assembly e l'attraversamento di spazi dei nomi all'interno per risolvere il tipo di classe che sto cercando. Qualche consiglio o suggerimento per svolgere questo compito con più garbo?


Risposte:


171

Dovrai aggiungere il nome dell'assembly in questo modo:

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

Per evitare ambiguità o se l'assembly si trova nella GAC, è necessario fornire un nome completo dell'assembly come tale:

Type.GetType("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

Eccellente, sapevo che mi mancava qualcosa di minore come includere l'assemblaggio. Questa soluzione ha funzionato per le mie esigenze. Grazie.
Brandon

11
E per coloro che si occupano di serializzazione: per ottenere il nome qualificato dall'assembly, c'è la proprietà Type.AssemblyQualifiedName
Michael Wild

1
Se il tipo è List <T>, dove T è una classe personalizzata, come si specificano 2 assembly? Vale a dire. L'assembly mscorlib per System.Collections.Generic.List e la libreria che contiene T?
Simon Green

@ SimonGreen: Probabilmente dovrai costruirlo usando listType.MakeGenericType(itemType). Entrambe le variabili di tipo possono essere costruite usando Type.GetType()come nella mia risposta.
Sandor Drieënhuizen

object.Assembly.ToString () Può essere utilizzato anche per ottenere l'assembly completo.
zezba9000

7

Questa soluzione universale è per le persone che hanno bisogno di caricare i tipi generici da dinamiche riferimenti esterni da parte AssemblyQualifiedName, senza sapere da cui assemblaggio sono tutte le parti di tipo generico provenienti da:

    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 puoi testarlo con questo codice (app 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();
    }

Condivido la mia soluzione per aiutare le persone con il mio stesso problema (per deserializzare QUALSIASI tipo dalla stringa che potrebbe essere definita sia parzialmente che nel suo insieme in un assembly referenziato esternamente - ei riferimenti vengono aggiunti dinamicamente dall'utente dell'app).

Spero che aiuti chiunque!


2

Simile all'OP, avevo bisogno di caricare un sottoinsieme limitato di tipi per nome (nel mio caso tutte le classi erano in un singolo assembly e implementavano la stessa interfaccia). Ho avuto molti problemi strani durante il tentativo di utilizzareType.GetType(string) su un assembly diverso (anche aggiungendo AssemblyQualifiedName come menzionato in altri post). Ecco come ho risolto il problema:

Utilizzo:

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

Codice:

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

Ovviamente potresti modificare il metodo CacheTypes per ispezionare tutti gli assembly in AppDomain o altra logica che si adatta meglio al tuo caso d'uso. Se il tuo caso d'uso consente il caricamento dei tipi da più spazi dei nomi, potresti voler cambiare la chiave del dizionario per utilizzare invece il tipo FullName. Oppure, se i tuoi tipi non ereditano da un'interfaccia comune o da una classe base, puoi rimuovere <BaseType>e modificare il metodo CacheTypes per utilizzare qualcosa di simile.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")


1

Caricare prima l'assieme e poi il tipo. ex: Assembly DLL = Assembly.LoadFile (PATH); DLL.GetType (typeName);


0

Puoi usare uno dei metodi standard?

typeof( MyClass );

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

In caso contrario, sarà necessario aggiungere informazioni al Type.GetType sull'assembly.


0

Approccio breve e dinamico che utilizza la AssemblyQualifiedNameproprietà -

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

Godere!


10
Se Type.GetType ("MyProject.Domain.Model." + MyClassName) non riesce, come può impedirlo il wrapping in un'altra chiamata GetType?
Kaine

1
In ogni caso, puoi racchiuderlo in un blocco try catch con un'eccezione System.NullReferenceException. È molto più probabile che si sbagli in questo - "MyProject.Domain.Model.ClassName, ClassName, Version = 2.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089" quindi in questo - "MyProject.Domain.Model." ...
simonbor

1
@Kaine Non sono sicuro di cosa significasse simonbor, ma se usi GetType (). AssemblyQualifiedName quando SCRIVI la stringa, non devi preoccuparti quando usi la stringa per risolvere un tipo.
Sergio Porres
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.