Rozwiąż typ z nazwy klasy w innym zestawie


87

Mam metodę, w której muszę rozwiązać typ klasy. Ta klasa istnieje w innym zestawie z przestrzenią nazw podobną do:

MyProject.Domain.Model

Próbuję wykonać następujące czynności:

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

Działa to świetnie, jeśli kod, który wykonuje tę akcję, znajduje się w tym samym zestawie, co klasa, której typ próbuję rozwiązać, jednak jeśli moja klasa znajduje się w innym zestawie, ten kod nie powiedzie się.

Jestem pewien, że istnieje znacznie lepszy sposób wykonania tego zadania, ale nie miałem zbyt dużego doświadczenia w rozwiązywaniu zestawów i przemierzaniu przestrzeni nazw w celu określenia typu klasy, której szukam. Jakieś rady lub wskazówki, jak wykonać to zadanie z większym wdziękiem?


Odpowiedzi:


171

Będziesz musiał dodać nazwę zestawu w następujący sposób:

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

Aby uniknąć niejednoznaczności lub jeśli zestaw znajduje się w GAC, należy podać w pełni kwalifikowaną nazwę zestawu, taką jak:

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

Doskonale, wiedziałem, że brakuje mi czegoś mniejszego, na przykład montażu. To rozwiązanie spełniło moje potrzeby. Dzięki.
Brandon

11
A dla osób zajmujących się serializacją: Aby uzyskać nazwę kwalifikującą się do zestawu, istnieje właściwość Type.AssemblyQualifiedName
Michael Wild

1
Jeśli typ jest List <T>, gdzie T jest klasą niestandardową, w jaki sposób określić 2 zestawy? To znaczy zestaw mscorlib dla System.Collections.Generic.List i biblioteka zawierająca T?
Simon Green

@SimonGreen: Prawdopodobnie będziesz musiał go zbudować za pomocą listType.MakeGenericType(itemType). Obie zmienne typu można skonstruować przy użyciu funkcji Type.GetType()like w mojej odpowiedzi.
Sandor Drieënhuizen

object.Assembly.ToString () Może służyć również do uzyskania pełnego zestawu.
zezba9000

7

To uniwersalne rozwiązanie jest dla ludzi, którzy potrzebują, aby załadować typy generyczne z dynamicznych odniesień zewnętrznych przez AssemblyQualifiedNamenie wiedząc, z której zespół są wszystkie elementy typu rodzajowego pochodzące z:

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

I można go przetestować z tym kodem (app konsoli):

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

Dzielę się moim rozwiązaniem, aby pomóc ludziom z tym samym problemem co ja (aby deserializować DOWOLNY typ z ciągu, który można zdefiniować zarówno częściowo, jak i jako całość w zestawie, do którego istnieją odwołania zewnętrzne - a odwołania są dynamicznie dodawane przez użytkownika aplikacji).

Mam nadzieję, że to pomoże każdemu!


2

Podobnie jak w przypadku OP, musiałem załadować ograniczony podzbiór typów według nazwy (w moim przypadku wszystkie klasy były w jednym zestawie i zaimplementowano ten sam interfejs). Podczas próby użycia miałem wiele dziwnych problemówType.GetType(string) przeciwko innym zestawom (nawet dodając AssemblyQualifiedName, jak wspomniano w innych postach). Oto jak rozwiązałem ten problem:

Stosowanie:

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

Kod:

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

Oczywiście można zmodyfikować metodę CacheTypes, aby sprawdzić wszystkie zestawy w AppDomain lub inną logikę, która lepiej pasuje do Twojego przypadku użycia. Jeśli twój przypadek użycia pozwala na ładowanie typów z wielu przestrzeni nazw, możesz chcieć zmienić klucz słownika, aby FullNamezamiast tego używał typu . Lub jeśli twoje typy nie dziedziczą ze wspólnego interfejsu lub klasy bazowej, możesz usunąć <BaseType>i zmienić metodę CacheTypes, aby użyć czegoś takiego jak.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")


1

Najpierw załaduj zespół, a następnie typ. ex: Assembly DLL = Assembly.LoadFile (PATH); DLL.GetType (typeName);


0

Czy możesz użyć jednego ze standardowych sposobów?

typeof( MyClass );

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

Jeśli nie, będziesz musiał dodać informacje do Type.GetType o zestawie.


0

Krótkie i dynamiczne podejście z wykorzystaniem AssemblyQualifiedNamenieruchomości -

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

Cieszyć się!


10
Jeśli Type.GetType ("MyProject.Domain.Model." + MyClassName) nie powiedzie się, jak zawinięcie go w inne wywołanie GetType może temu zapobiec?
Kaine

1
W każdym przypadku można go opakować w blok try catch z Wyjątkiem System.NullReferenceException. Znacznie bardziej prawdopodobne jest, że popełnisz błąd w tym - „MyProject.Domain.Model.ClassName, ClassName, Version = 2.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089” niż w tym - „MyProject.Domain.Model.” ...
simonbor

1
@Kaine Nie jestem pewien, co miał na myśli simonbor, ale jeśli użyjesz GetType (). AssemblyQualifiedName podczas PISANIA ciągu, nie musisz się o to martwić, gdy używasz ciągu do rozpoznawania typu.
Sergio Porres
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.