Resolve Type from Class Name in a Different Assembly

后端 未结 6 922
轮回少年
轮回少年 2020-11-27 17:32

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
         


        
6条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-11-27 18:00

    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(@"^(?\w+(\.\w+)*)`(?\d)\[(?\[.*\])\](, (?\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 typeNames = new List();
            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>>);
            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 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!

提交回复
热议问题