Lösen Sie den Typ aus dem Klassennamen in einer anderen Assembly

87

Ich habe eine Methode, mit der ich den Typ einer Klasse auflösen muss. Diese Klasse existiert in einer anderen Assembly mit dem Namespace ähnlich:

MyProject.Domain.Model

Ich versuche Folgendes auszuführen:

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

Dies funktioniert hervorragend, wenn sich der Code, der diese Aktion ausführt, in derselben Assembly befindet wie die Klasse, deren Typ ich auflösen möchte. Wenn sich meine Klasse jedoch in einer anderen Assembly befindet, schlägt dieser Code fehl.

Ich bin mir sicher, dass es einen weitaus besseren Weg gibt, um diese Aufgabe zu erfüllen, aber ich habe nicht viel Erfahrung mit dem Auflösen von Assemblys und dem Durchsuchen von Namespaces, um den Typ der gesuchten Klasse aufzulösen. Irgendwelche Ratschläge oder Tipps, um diese Aufgabe eleganter zu erledigen?

Brandon
quelle

Antworten:

170

Sie müssen den Namen der Assembly wie folgt hinzufügen:

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

Um Mehrdeutigkeiten zu vermeiden oder wenn sich die Baugruppe im GAC befindet, sollten Sie einen vollständig qualifizierten Baugruppennamen wie den folgenden angeben:

Type.GetType("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
Sandor Drieënhuizen
quelle
Ausgezeichnet, ich wusste, dass mir etwas Kleines wie die Montage fehlte. Diese Lösung hat für meine Bedürfnisse funktioniert. Vielen Dank.
Brandon
10
Und für diejenigen, die sich mit Serialisierung befassen: Um den Assembly-qualifizierten Namen zu erhalten, gibt es die Eigenschaft Type.AssemblyQualifiedName
Michael Wild
1
Wenn der Typ eine Liste <T> ist, wobei T eine benutzerdefinierte Klasse ist, wie geben Sie 2 Assemblys an? Dh die mscorlib-Assembly für System.Collections.Generic.List und die Bibliothek, die T?
Simon Green
@ SimonGreen: Du musst es wahrscheinlich mit konstruieren listType.MakeGenericType(itemType). Beide Typvariablen können Type.GetType()wie in meiner Antwort erstellt werden.
Sandor Drieënhuizen
object.Assembly.ToString () Kann verwendet werden, um auch die vollständige Assembly abzurufen.
zezba9000
5

Diese universelle Lösung ist für Personen gedacht, die generische Typen aus dynamischen externen Referenzen von laden müssen AssemblyQualifiedName, ohne zu wissen, von welcher Assembly alle Teile des generischen Typs stammen:

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

Und Sie können es mit diesem Code (Konsolen-App) testen :

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

Ich teile meine Lösung, um Menschen mit dem gleichen Problem wie mir zu helfen (um JEDEN Typ aus einer Zeichenfolge zu deserialisieren, die in einer extern referenzierten Assembly sowohl teilweise als auch als Ganzes definiert werden kann - und die Referenzen werden vom Benutzer der App dynamisch hinzugefügt).

Hoffe es hilft jedem!

PW
quelle
2

Ähnlich wie beim OP musste ich eine begrenzte Teilmenge von Typen nach Namen laden (in meinem Fall befanden sich alle Klassen in einer einzigen Assembly und implementierten dieselbe Schnittstelle). Ich hatte viele seltsame Probleme, als ich versuchte, sie Type.GetType(string)gegen eine andere Assembly zu verwenden (sogar das Hinzufügen des AssemblyQualifiedName, wie in anderen Beiträgen erwähnt). So habe ich das Problem gelöst:

Verwendung:

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

Natürlich können Sie die CacheTypes-Methode optimieren, um alle Assemblys in der AppDomain oder eine andere Logik zu überprüfen, die besser zu Ihrem Anwendungsfall passt. Wenn in Ihrem Anwendungsfall das Laden von Typen aus mehreren Namespaces möglich ist, möchten Sie möglicherweise den Wörterbuchschlüssel ändern, um FullNamestattdessen die Typen zu verwenden. Oder wenn Ihre Typen nicht von einer gemeinsamen Schnittstelle oder Basisklasse erben, können Sie <BaseType>die CacheTypes-Methode entfernen und ändern, um so etwas wie zu verwenden.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")

Immer präsent
quelle
1

Laden Sie zuerst die Baugruppe und dann den Typ. Beispiel: Assembly DLL = Assembly.LoadFile (PATH); DLL.GetType (typeName);

azulay7
quelle
0

Können Sie eine der Standardmethoden verwenden?

typeof( MyClass );

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

Wenn nicht, müssen Sie dem Typ.GetType Informationen zur Assembly hinzufügen.

Jerod Houghtelling
quelle
0

Kurzer und dynamischer Ansatz mit AssemblyQualifiedNameEigenschaft -

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

Genießen!

Simonbor
quelle
10
Wenn Type.GetType ("MyProject.Domain.Model." + MyClassName) fehlschlägt, wie kann das Umschließen in einen anderen GetType-Aufruf dies verhindern?
Kaine
1
In jedem Fall können Sie es mit einer System.NullReferenceException in einen try catch-Block einschließen. Viel wahrscheinlicher ist dies - "MyProject.Domain.Model.ClassName, ClassName, Version = 2.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089" als in diesem - "MyProject.Domain.Model." ...
Simonbor
1
@Kaine Ich bin nicht sicher, was simonbor bedeutet, aber wenn Sie beim Schreiben der Zeichenfolge GetType (). AssemblyQualifiedName verwenden, müssen Sie sich darüber keine Gedanken machen, wenn Sie die Zeichenfolge zum Auflösen in einen Typ verwenden.
Sergio Porres