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

Tri*_*nko 2 .net c# reflection formatting typename

我一直在研究Type.NamespaceType.NameType.FullName和的返回值Type.AssemblyQualifiedName。存在不一致之处。

对于像 这样的内部类ConsoleApplication8.Program+InnerClass,命名空间返回ConsoleApplication8Name返回InnerClass,省略Program,因此连接Type.NameSpaceType.Name将是类名的不完整表示(仅作为示例)。

就连FullName属性也不一致。尽管它省略了程序集名称并返回ConsoleApplication8.Program+InnerClass这样的内部类,但FullName在诸如此类的类型的泛型参数中包含程序集名称List<long>(即使外部泛型类型本身省略了程序集名称,所以我猜那里存在一定程度的一致性)。

我目前正在将此代码与缓存类型名称查找一起使用,该查找使用 CodeDom 生成真正的 C# 代码名称。基本上,我试图扭转这个过程来获取类型,给定一个真实的类名。

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;
}
Run Code Online (Sandbox Code Playgroud)

上面的函数生成友好的 C# 名称,例如System.Collections.Generic.List<long>. 但它也产生类似的名称(即它在和ConsoleApplication8.Program.InnerClass之间使用点而不是加号)。问题是调用不起作用,因为它需要加号,而且有时还需要程序集名称。ProgramInnerClassType.GetType(name)

Type那么,如果给定一个友好的 C# 类名(就像在代码中引用的那样),如何获取对对象的引用呢?

Tri*_*nko 6

我现在已经成功地通过在每个方向上的一行代码来实现这一点,在友好类型名称和运行时类型实例之间进行转换。极好的。还有人说这根本不可能,哈哈。完美无瑕。

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));
}
Run Code Online (Sandbox Code Playgroud)

上面的代码(仅限第一种方法)在扩展到多行时如下所示。你可以像这样进行调用GetType("System.Collections.Generic.List<int>");,它会返回一个类型引用。

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 );
}
Run Code Online (Sandbox Code Playgroud)

更新:我添加了一行以使当前应用程序域中加载的所有模块可供编译器使用。这应该保证它能够通过名称获取任何类型,就像您在代码中直接引用它一样,但所需的类型必须是公共的,因为它们本质上是从编译器内部的外部程序集引用的,而不是而不是直接在当​​前正在执行的程序集中。

正如测试一样,返回的类型应该在缓存中工作,因为:

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