在不同的程序集中从类名解析类型


87

我有一种方法需要解析类的类型。此类存在于另一个程序集中,其名称空间类似于:

MyProject.Domain.Model

我正在尝试执行以下操作:

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

如果执行此操作的代码与我要解析其类型的类在同一程序集中,则此方法非常有用,但是,如果我的类在其他程序集中,则此代码将失败。

我确信有更好的方法可以完成此任务,但是我在解析程序集和遍历名称空间以解决我要查找的类的类型方面没有很多经验。有什么建议或提示可以更优雅地完成此任务吗?


Answers:


171

您必须像这样添加程序集名称:

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

为避免歧义或如果程序集位于GAC中,则应提供一个完全合格的程序集名称,例如:

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

太好了,我知道我缺少一些像组装这样的小东西。此解决方案满足了我的需求。谢谢。
布兰登

10
对于那些处理序列化的人:要获得程序集合格的名称,请使用属性Type.AssemblyQualifiedName
Michael Wild

1
如果类型是List <T>,其中T是自定义类,那么如何指定2个程序集?即System.Collections.Generic.List的.mscorlib程序集,以及包含T的库?
西蒙·格林

@SimonGreen:您可能必须使用构建它listType.MakeGenericType(itemType)。这两个类型变量都可以Type.GetType()在我的答案中使用like来构造。
SandorDrieënhuizen17年

object.Assembly.ToString()也可用于获取完整的程序集。
zezba9000

6

这种普遍的解决方案是谁需要加载的人泛型类型从动态的外部引用AssemblyQualifiedName,不知道从哪个组件是通用型的所有部分来自何处:

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

而且你可以用这个代码测试(控制台应用程序):

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

我正在分享我的解决方案,以帮助遇到与我相同的问题的人(从可以在外部引用的程序集中部分或整体定义的字符串中反序列化任何类型,并且这些引用由应用程序的用户动态添加)。

希望它能帮助任何人!


2

与OP相似,我需要按名称加载类型的有限子集(在我的情况下,所有类都在单个程序集中并实现相同的接口)。尝试Type.GetType(string)对其他程序集使用时,我遇到了很多奇怪的问题(甚至添加了其他帖子中提到的AssemblyQualifiedName)。这是我解决问题的方法:

用法:

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

码:

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

显然,您可以调整CacheTypes方法来检查AppDomain中的所有程序集,或者检查其他更适合您的用例的逻辑。如果您的用例允许从多个名称空间中加载类型,则可能需要更改字典键以使用该类型的键FullName。或者,如果您的类型不继承自通用接口或基类,则可以删除<BaseType>并更改CacheTypes方法以使用类似.GetTypes().Where(t => t.Namespace.StartsWith("MyProject.Domain.Model.")


1

首先加载装配体,然后加载类型。例如:Assembly DLL = Assembly.LoadFile(PATH); DLL.GetType(typeName);


0

您可以使用任何一种标准方法吗?

typeof( MyClass );

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

如果不是,则必须将有关程序集的信息添加到Type.GetType。


0

使用AssemblyQualifiedName属性的简短而动态的方法-

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

请享用!


10
如果Type.GetType(“ MyProject.Domain.Model。” + myClassName)失败,如何将其包装在另一个GetType调用中可以防止这种情况发生?
凯恩

1
无论如何,您都可以使用System.NullReferenceException将其包装在try catch块中。更有可能在此-“ MyProject.Domain.Model.ClassName,ClassName,Version = 2.0.0.0,Culture = neutral,PublicKeyToken = b77a5c561934e089”错误,然后在此-“ MyProject.Domain.Model。” ...
simonbor

1
@Kaine我不确定simonbor是什么意思,但是如果在写入字符串时使用GetType()。AssemblyQualifiedName,则在使用字符串解析为类型时不必担心。
塞尔吉奥·波雷斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.