1 votes

Résoudre le type à partir du nom de la classe dans un assemblage différent

J'ai une méthode où je dois résoudre le type d'une classe. Cette classe existe dans un autre assemblage dont l'espace de noms est similaire à :

MyProject.Domain.Model

J'essaie d'effectuer ce qui suit :

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

Cela fonctionne très bien si le code qui exécute cette action se trouve dans la même assemblée que la classe dont j'essaie de résoudre le type, mais si ma classe se trouve dans une assemblée différente, ce code échoue.

Je suis sûr qu'il existe un meilleur moyen d'accomplir cette tâche, mais je n'ai pas beaucoup d'expérience en matière de résolution d'assemblages et de traversée d'espaces de noms à l'intérieur de ceux-ci pour déterminer le type de la classe que je recherche. Avez-vous des conseils ou des astuces pour accomplir cette tâche de manière plus élégante ?

192voto

Sandor Drieënhuizen Points 2570

Vous devrez ajouter le nom de l'assemblage comme ceci :

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

Pour éviter toute ambiguïté ou si l'assemblage est situé dans le GAC, vous devez fournir un nom d'assemblage pleinement qualifié comme celui-ci :

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

8voto

P.W. Points 47

Cette solution universelle est destiné aux personnes qui ont besoin de charger types génériques à partir de références externes dynamiques par AssemblyQualifiedName sans savoir de quel assemblage proviennent toutes les pièces du type générique :

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

Et vous pouvez le tester avec ce code (application 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();
    }

Je partage ma solution pour aider les personnes qui ont le même problème que moi (pour désérialiser N'IMPORTE QUEL type de chaîne de caractères qui pourrait être défini à la fois partiellement ou en totalité dans une assemblée référencée en externe - et les références sont ajoutées dynamiquement par l'utilisateur de l'application).

J'espère que ça aidera tout le monde !

2voto

azulay7 Points 194

Chargez d'abord l'assemblage, puis le type.

Ejemplo:

Assembly DLL = Assembly.LoadFile(PATH);
DLL.GetType(typeName);

2voto

EverPresent Points 1298

Comme dans le cas de l'OP, j'avais besoin de charger un sous-ensemble limité de types par nom (dans mon cas, toutes les classes étaient dans un seul assemblage et implémentaient la même interface). J'ai rencontré beaucoup de problèmes étranges en essayant d'utiliser la fonction Type.GetType(string) contre un assemblage différent (même en ajoutant le AssemblyQualifiedName comme mentionné dans d'autres articles). Voici comment j'ai résolu le problème :

Utilisation :

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

Code :

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

Vous pouvez évidemment modifier la méthode CacheTypes pour inspecter tous les assemblages dans le domaine d'application, ou toute autre logique qui correspond mieux à votre cas d'utilisation. Si votre cas d'utilisation permet aux types d'être chargés à partir de plusieurs espaces de noms, vous pouvez modifier la clé du dictionnaire pour utiliser le nom de l'espace de noms du type. FullName à la place. Ou, si vos types n'héritent pas d'une interface ou d'une classe de base commune, vous pouvez supprimer l'attribut <BaseType> et changez la méthode CacheTypes pour utiliser quelque chose comme .GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")

0voto

Jerod Houghtelling Points 2426

Pouvez-vous utiliser l'une ou l'autre des méthodes standard ?

typeof( MyClass );

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

Si ce n'est pas le cas, vous devrez ajouter des informations au Type.GetType concernant l'assemblage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X