如何使用反射获得泛型类型的正确文本定义?

26

我正在进行代码生成工作,并在泛型方面遇到了问题。以下是导致问题的“简化”版本。

Dictionary<string, DateTime> dictionary = new Dictionary<string, DateTime>();
string text = dictionary.GetType().FullName;

使用上面的代码片段,text 的值如下:

 System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, 
 Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.DateTime, mscorlib, 
 Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

有没有一种方法可以在不解析上述字符串的情况下以不同的格式获取类型名(type)?我希望text的结果如下:

System.Collections.Generic.Dictionary<System.String, System.DateTime>

2
请注意,如果您删除 .FullName 并改用 .ToString(),则会得到更易读且接近所需的 "text" System.Collections.Generic.Dictionary`2[System.String,System.DateTime] - Jeppe Stig Nielsen
6个回答

32

在.Net Framework中没有内置的方法可以获取这种表示。主要是因为没有办法正确地获取它。有很多构造体在C#风格语法中不能表示。例如"<>foo"是IL中的有效类型名称,但无法在C#中表示。

然而,如果你正在寻找一个相当不错的解决方案,可以手动快速实现。下面的解决方案将适用于大多数情况。它将无法处理:

  1. 嵌套类型
  2. 非法的C#名称
  3. 另外一些情况

例子:

public static string GetFriendlyTypeName(Type type) {
    if (type.IsGenericParameter)
    {
        return type.Name;
    }

    if (!type.IsGenericType)
    {
        return type.FullName;
    }

    var builder = new System.Text.StringBuilder();
    var name = type.Name;
    var index = name.IndexOf("`");
    builder.AppendFormat("{0}.{1}", type.Namespace, name.Substring(0, index));
    builder.Append('<');
    var first = true;
    foreach (var arg in type.GetGenericArguments())
    {
        if (!first)
        {
            builder.Append(',');
        }
        builder.Append(GetFriendlyTypeName(arg));
        first = false;
    }
    builder.Append('>');
    return builder.ToString();
}

如果你有机会编辑你的答案并在代码块中包含 "static string GetFriendlyTypeName(Type type) { if (type.IsGenericParameter) { return type.Name; }"。 - Jamey McElveen
@Jamey,搞定了。这实际上相当奇怪。在我在有序列表和代码块开始之间添加非代码行之前,代码块不会添加第一行。 - JaredPar
支持数组的翻译内容如下:bool bArray = false; 如果 (type.IsArray) { bArray = true; type = type.GetElementType(); } 如果 (bArray) builder.Append("[]"); return builder.ToString(); - Wolf5
你可能想要处理可空类型,同时在逗号后面加上一个空格。 - SLaks
如果 (type.IsGenericParameter) {...} 这个代码块可能不是一个好主意,因为泛型类型参数本身可能是一个泛型类型。 - Johnny5
类似 GetFriendlyTypeName(new List<int>().GetEnumerator().GetType()) 或者 GetFriendlyTypeName(typeof(List<>.Enumerator)) 会导致异常。这些都是嵌套在泛型外部类型中的类型。 - Jeppe Stig Nielsen

23

感谢@LukeH的评论,一个好的、干净的替代方案是

using System;
using System.CodeDom;
using System.Collections.Generic;
using Microsoft.CSharp;
//...
private string GetFriendlyTypeName(Type type)
{
    using (var p = new CSharpCodeProvider())
    {
        var r = new CodeTypeReference(type);
        return p.GetTypeOutput(r);
    }
}

4
比标记为答案的要好得多(泛型类的子类显示正确)。 - FLCL

4
今晚我花了点时间玩弄扩展方法,并试图找到你提出的问题的答案。这是结果:这是一段无保障的代码。;-)
internal static class TypeHelper
{
    private const char genericSpecialChar = '`';
    private const string genericSeparator = ", ";

    public static string GetCleanName(this Type t)
    {
        string name = t.Name;
        if (t.IsGenericType)
        {
            name = name.Remove(name.IndexOf(genericSpecialChar));
        }
        return name;
    }

    public static string GetCodeDefinition(this Type t)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0}.{1}", t.Namespace, t.GetCleanName());
        if (t.IsGenericType)
        {
            var names = from ga in t.GetGenericArguments()
                        select GetCodeDefinition(ga);
            sb.Append("<");
            sb.Append(string.Join(genericSeparator, names.ToArray()));
            sb.Append(">");
        }
        return sb.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        object[] testCases = { 
                                new Dictionary<string, DateTime>(),
                                new List<int>(),
                                new List<List<int>>(),
                                0
                            };
        Type t = testCases[0].GetType();
        string text = t.GetCodeDefinition();
        Console.WriteLine(text);
    }
}

2
string text = dictionary.ToString();

这个提供了你几乎所需求的:

System.Collections.Generic.Dictionary`2[System.String,System.DateTime]

这不是通用解决方案。任何使用.ToString()的类型都会破坏此解决方案。 - JaredPar
@JaredPar 可以通过使用 dictionary.GetType().ToString() 来解决这个问题。 - Jeppe Stig Nielsen

0

我认为.NET没有内置任何可以做到这一点的东西,因此您必须自己完成。我认为反射类提供了足够的信息来以这种形式重构类型名称。


我已经尝试了这个方法,你能详细说明一下解决方案吗?我没有找到任何属性或集合可以提供我所需的信息,以便重建声明。 - Jamey McElveen

0

我相信你能通过

System.String,mscorlib,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089

可以使用 Type.Parse() 将其转换为完全限定类型名称。


原始的Dictionary.GetType()已经包含了类型参数的集合,您可以直接访问它们,无需解析即可获得相同的结果。 - Vilx-

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接