113

I have a method where I need to resolve the Type of a class. This class exists in another assembly with the namespace similar to:

MyProject.Domain.Model

I am attempting to perform the following:

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

This works great if the code that is performing this action is in the same assembly as the class whose type I am trying to resolve, however, if my class is in a different assembly, this code fails.

I am sure there is a far better way to accomplish this task, but I have not had a lot of experience with resolving assemblies and traversing namespaces within to resolve the type of the class I am looking for. Any advice or tips to accomplish this task more gracefully?

Brandon
  • 10,744
  • 18
  • 64
  • 97
  • possible duplicate of [Type.GetType("namespace.a.b.ClassName") returns null](http://stackoverflow.com/questions/1825147/type-gettypenamespace-a-b-classname-returns-null) –  Apr 08 '15 at 22:29

6 Answers6

209

You'll have to add the assembly name like this:

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

To avoid ambiguity or if the assembly is located in the GAC, you should provide a fully qualified assembly name like such:

Type.GetType("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
Sandor Drieënhuizen
  • 6,310
  • 5
  • 37
  • 80
  • 1
    Excellent, I knew I was missing something minor like including the assembly. This solution worked for my needs. Thanks. – Brandon Aug 18 '10 at 13:21
  • 15
    And for those dealing in serialization: To obtain the assembly-qualified name, there is the property [Type.AssemblyQualifiedName](http://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname.aspx) – Michael Wild Apr 16 '14 at 09:28
  • 1
    If the type is a List , where T is a custom class, how you do specify 2 assemblies? I.e .the mscorlib assembly for System.Collections.Generic.List, and the library that contains T? – Simon Green Jun 19 '17 at 10:38
  • @SimonGreen: You'll probably have to construct it using `listType.MakeGenericType(itemType)`. Both type variables can be constructed using `Type.GetType()` like in my answer. – Sandor Drieënhuizen Jun 23 '17 at 10:35
  • object.Assembly.ToString() Can be used to also get the full assembly. – zezba9000 Sep 13 '18 at 23:37
  • @zezba9000 surely you mean `obj.GetType().Assembly.ToString()`? – Sandor Drieënhuizen Sep 27 '18 at 11:58
  • oops yes, meant .Assembly.ToString() as in Type.Assembly.ToString() – zezba9000 Sep 27 '18 at 23:52
  • @SimonGreen I have posted my solution for this problem below. – P.W. Nov 26 '18 at 16:33
  • How would you do this if the type you are loading is a genetic one, where the string syntax would have ...`1[...]" sort of thingie? – Ε Г И І И О Jun 24 '21 at 07:59
  • what if the type is being loaded in another assembly but that other assembly is from nuget and can't be changed? It still fails, when deployed at least. Locally works great. – ed22 Nov 23 '22 at 10:10
9

This universal solution is for people who need to load generic types from dynamic external references by AssemblyQualifiedName, without knowing from which assembly are all parts of generic type coming from:

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

And you can test it with this code (console app):

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

I'm sharing my solution to help people with the same problem as me (to deserialize ANY type from string that could be defined both partially or as a whole in externally referenced assembly - and the references are dynamically added by app's user).

Hope it helps anyone!

P.W.
  • 657
  • 8
  • 15
3

First load the assembly and then the type.

Example:

Assembly DLL = Assembly.LoadFile(PATH);
DLL.GetType(typeName);
Pang
  • 9,564
  • 146
  • 81
  • 122
azulay7
  • 305
  • 3
  • 16
2

Similar to the OP, I needed to load a limited subset of types by name (in my case all of the classes were in a single assembly and implemented the same interface). I had a lot of weird issues when trying to use Type.GetType(string) against a different assembly (even adding the AssemblyQualifiedName as mentioned in other posts). Here is how I solved the issue:

Usage:

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

Obviously you could tweak the CacheTypes method to inspect all assemblies in the AppDomain, or other logic that better fits your use-case. If your use-case allows for types to be loaded from multiple namespaces, you might want to change the dictionary key to use the type's FullName instead. Or if your types don't inherit from a common interface or base class, you could remove the <BaseType> and change the CacheTypes method to use something like .GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")

EverPresent
  • 1,903
  • 17
  • 21
0

Can you use either of the standard ways?

typeof( MyClass );

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

If not, you will have to add information to the Type.GetType about the assembly.

Jerod Houghtelling
  • 4,783
  • 1
  • 22
  • 30
-1

Short and dynamic approach using AssemblyQualifiedName property -

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

Enjoy!

Simon Borsky
  • 4,979
  • 2
  • 22
  • 20
  • 13
    If Type.GetType("MyProject.Domain.Model." + myClassName) fails, how can wrapping it in another GetType call prevent that? – Kaine May 01 '18 at 14:33
  • 1
    In any case, you can wrap it in a try catch block with a System.NullReferenceException. Much more likely to be mistaken in this - "MyProject.Domain.Model.ClassName, ClassName, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" then in this - "MyProject.Domain.Model."... – Simon Borsky Aug 29 '18 at 13:09
  • 2
    @Kaine I am not sure what simonbor meant, but if you use GetType().AssemblyQualifiedName when WRITING the string, then you dont have to worry about it when using the string to resolve to a type. – Sergio Porres Nov 20 '19 at 02:12