简体   繁体   English

如何从C#类型名称字符串获取类型?

[英]How to get a Type from a C# type name string?

I've been looking at return values for Type.Namespace , Type.Name , Type.FullName , and Type.AssemblyQualifiedName . 我一直在寻找Type.NamespaceType.NameType.FullNameType.AssemblyQualifiedName返回值。 There are inconsistencies. 有不一致之处。

For an inner class like ConsoleApplication8.Program+InnerClass , Namespace returns ConsoleApplication8 and Name returns InnerClass , omitting Program , so concatenating Type.NameSpace and Type.Name would be an incomplete representation of the class name (just as an example). 对于类似ConsoleApplication8.Program+InnerClass的内部类,名称空间返回ConsoleApplication8Name返回InnerClass ,省略Program ,因此将Type.NameSpaceType.Name串联Type.Name将是类名的不完整表示(仅作为示例)。

Even the FullName property is inconsistent. 甚至FullName属性也不一致。 Although it omits assembly name and returns ConsoleApplication8.Program+InnerClass for such an inner class, FullName includes the assembly name in the generic arguments for a type such as List<long> (even though it's omitted for the outer generic type itself, so I guess there is some level of consistency there). 尽管它省略了程序集名称并为此类内部类返回ConsoleApplication8.Program+InnerClass ,但是FullName在诸如List<long>的类型的通用参数中包括了程序集名称(即使外部通用类型本身被省略了,所以我猜测那里有一定程度的一致性)。

I am currently using this code with a cached type name lookup that uses the CodeDom to produce real C# code names. 我目前正在将此代码与使用CodeDom生成真实C#代码名称的缓存类型名称查找一起使用。 Basically, I'm trying to reverse the process to get the type, given a real class name. 基本上,在给定真实的类名的情况下,我试图反转过程以获取类型。

static System.Collections.Concurrent.ConcurrentDictionary<Type, string> typeNameCache = new System.Collections.Concurrent.ConcurrentDictionary<Type, string>();

static string GetTypeName(Type type)
{
    string name;
    if (!typeNameCache.TryGetValue( type, out name ))
    {
        var codeDomProvider = CodeDomProvider.CreateProvider("C#");
        var typeReferenceExpression = new CodeTypeReferenceExpression(new CodeTypeReference(type));
        using (var writer = new StringWriter())
        {
            codeDomProvider.GenerateCodeFromExpression(typeReferenceExpression, writer, new CodeGeneratorOptions());
            name = writer.GetStringBuilder().ToString();
        }
        typeNameCache.TryAdd( type, name );
    }
    return name;
}

The above function produces friendly C# name like System.Collections.Generic.List<long> . 上面的函数产生友好的C#名称,例如System.Collections.Generic.List<long> But it also produces names like ConsoleApplication8.Program.InnerClass (ie it uses a dot instead of a plus sign between Program and InnerClass ). 但是它还会产生类似ConsoleApplication8.Program.InnerClass名称(即,它在ProgramInnerClass之间使用点而不是加号)。 The problem is that calling Type.GetType(name) won't work, because it would require the plus sign to be there, and in addition it sometimes requires the assembly name. 问题在于调用Type.GetType(name)将不起作用,因为它将需要加号,并且有时还需要程序集名称。

So how can I get a references to a Type object, given a friendly C# class name as it would be referenced in code? 那么,如何给定Type对象的引用(给定一个友好的C#类名,因为它将在代码中被引用)?

I've managed to achieve this now with a single line of code in each direction translating to and from friendly type names and runtime Type instances. 现在,我已经成功地通过在每个方向上与友好类型名称和运行时Type实例之间进行转换的一行代码来实现这一目标。 Incredible. 难以置信。 And some said it was not possible at all, lol. 有人说根本不可能,哈哈。 Flawless. 无瑕。

static Type GetType( string friendlyName )
{
    return (Type)(new CSharpCodeProvider().CompileAssemblyFromSource( new CompilerParameters( AppDomain.CurrentDomain.GetAssemblies().SelectMany<Assembly,string>( a => a.GetModules().Select<Module,string>( m => m.FullyQualifiedName )).ToArray(), null, false) {GenerateExecutable = false, GenerateInMemory = true, TreatWarningsAsErrors = false, CompilerOptions = "/optimize"}, "public static class C{public static System.Type M(){return typeof(" + friendlyName + ");}}").CompiledAssembly.GetExportedTypes()[0].GetMethod("M").Invoke( null, System.Reflection.BindingFlags.Static, null, null, null ));
}

static string GetFriendlyName( Type type )
{
    return new CSharpCodeProvider().GetTypeOutput(new CodeTypeReference(type));
}

The above code (first method only), when expanded to multiple lines looks like the following. 上面的代码(仅第一种方法),当扩展为多行时,如下所示。 You can just make a call like GetType("System.Collections.Generic.List<int>"); 您可以像GetType("System.Collections.Generic.List<int>");那样进行调用GetType("System.Collections.Generic.List<int>"); and it will return a type reference. 它将返回一个类型引用。

static Type GetType( string friendlyName )
{
    var currentlyLoadedModuleNames = AppDomain.CurrentDomain.GetAssemblies().SelectMany<Assembly,string>( a => a.GetModules().Select<Module,string>( m => m.FullyQualifiedName )).ToArray();
    var csc = new CSharpCodeProvider();
    CompilerResults results = csc.CompileAssemblyFromSource(
        new CompilerParameters( currentlyLoadedModuleNames, "temp.dll", false) {
            GenerateExecutable = false, GenerateInMemory = true, TreatWarningsAsErrors = false, CompilerOptions = "/optimize"
        },
        @"public static class TypeInfo {
            public static System.Type GetEmbeddedType() {
            return typeof(" + friendlyName + @");
            }
        }");
    if (results.Errors.Count > 0)
        throw new Exception( "Error compiling type name." );
    Type[] type = results.CompiledAssembly.GetExportedTypes();
    return (Type)type[0].GetMethod("GetEmbeddedType").Invoke( null, System.Reflection.BindingFlags.Static, null, null, null );
}

Update: I added a line to make all the modules loaded in the current app domain available to the compiler. 更新:我添加了一行代码,以使编译器可以使用当前应用程序域中加载的所有模块。 That should guarantee that this is able to acquire any type by name as if you had referenced it directly in your code, with the exception that the desired types have to be public, since they are essentially referenced from an external assembly from within the compiler rather than directly in the currently executing assembly. 那应该保证它能够按名称获取任何类型,就好像您已经在代码中直接引用了它一样,只是所需的类型必须是公共的,因为它们实际上是从编译器内部的外部程序集引用而来的而不是直接在当​​前执行的程序集中。

Just as as test, the returned type should work in a cache, since: 就像测试一样,返回的类型应该在缓存中工作,因为:

Type t = GetType( "System.Collections.Generic.List<int>" );
Console.WriteLine( typeof(System.Collections.Generic.List<int>) == t );
//RETURNS TRUE

So, basically per our conversation in the comments the goal here is to get a type object (for whatever purpose) from a name of the type. 因此,基本上,根据我们在注释中的对话,此处的目标是从类型名称中获取类型对象(出于任何目的)。 Calling "Type.GetType()" works for simple types but for generics and other types it doesn't work because the name needs to be qualified. 调用“ Type.GetType()”适用于简单类型,但不适用于泛型和其他类型,因为名称需要限定。 The key, then is to use the code compiler to actually have the C# code engine find and get the type. 然后,关键是使用代码编译器实际让C#代码引擎查找并获取类型。 The following is a working program that does just that: 以下是一个可以执行此操作的程序:

using System;
using System.Reflection;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

namespace SimpleCompileTest
{
    class Program
    {
        public static void Main(string[] args)
        {
            string typeName = "System.Collections.Generic.List<int>";
            Type theType = GetTypeFromName(typeName);
        }

        private static Type GetTypeFromName(string typeName)
        {
            // double open and close are for escape purposes
            const string typeProgram = @"using System; using System.Collections.Generic; using System.IO;
                namespace SimpleTest
                {{
                    public class Program
                    {{
                        public static Type GetItemType()
                        {{
                            {0} typeTest = new {0}();
                            if (typeTest == null) return null;
                            return typeTest.GetType();
                        }}
                    }}
                }}";

            var formattedCode = String.Format(typeProgram, typeName);

            var CompilerParams = new CompilerParameters
                {
                    GenerateInMemory = true,
                    TreatWarningsAsErrors = false,
                    GenerateExecutable = false,
                    CompilerOptions = "/optimize"
                };

            string[] references = { "System.dll" };
            CompilerParams.ReferencedAssemblies.AddRange(references);

            var provider = new CSharpCodeProvider();
            CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, formattedCode);
            if (compile.Errors.HasErrors) return null;


            Module module = compile.CompiledAssembly.GetModules()[0];
            Type mt = null; MethodInfo methInfo = null;

            if (module != null) mt = module.GetType("SimpleTest.Program");
            if (mt != null) methInfo = mt.GetMethod("GetItemType");
            if (methInfo != null) return (Type)methInfo.Invoke(null, null);

            return null;
        }
    }
}

One really important thing to note - you need to add the list of assemblies to the compiler that you hope to pull types from. 需要注意的一件非常重要的事情-您需要将汇编程序列表添加到希望从中提取类型的编译器。 That means if you have a custom type that you want to reference you need to provide that to the compiler, but otherwise, this works! 这意味着,如果您有要引用的自定义类型,则需要将其提供给编译器,否则,它将起作用! Enjoy! 请享用!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM