使用 C 上的 Reflection 以良好的格式获取 GenericType-Name#

Get GenericType-Name in good format using Reflection on C#

提问人:AndreyAkinshin 提问时间:10/8/2009 最后编辑:AndreyAkinshin 更新时间:11/17/2023 访问量:5969

问:

我需要在代码中以声明的形式获取泛型类型的名称。

例如: 对于 List<Int32>我想获取字符串“List<Int32>”。 在这种情况下,标准属性 Type.Name 返回“List'1”。

编辑:示例已修复

C# .NET 反射

评论


答:

-1赞 Maximilian Mayerl 10/8/2009 #1

嗯,这是因为 .NET 中的类型名称实际上是 List'1。“'1”是泛型的所谓arity,它告诉你有多少个类型参数。

它是必要的,以便您可以创建多个具有相同“名称”但不同数量的泛型类型参数的泛型类型。

例如,有 1 种以上的类型“称为”System.Action。它们的真实名称是 System.Action'1、System.Action'2、System.Action'3 等。

所以,如果你知道你的类型是泛型的,你可以假设名称的末尾有这个'XX,所以你可以把这部分剪掉,例如,像这样:

string strTypeName = typeof(List<>).Name.Substring(0, typeof(List<>).Name.LastIndexOf("`"));

PS:请将“替换为”。

评论

0赞 AndreyAkinshin 10/8/2009
对不起,我的例子中有一个错误。我需要获取泛型类型的参数(在我的示例中:Int32)
0赞 ToolmakerSteve 8/7/2017
这个答案不再相关(在问题被编辑之后)。
14赞 Erich 10/8/2009 #2

好的,我做了很多研究,发现typeof(List)有“GetGenericArguments”,可以让你得到子名。所以我会这样做(对于 1 个泛型类型,如果它是多重类型,它将需要一个循环或其他东西。如果需要,我可以为此发布一个功能。

这是一个使用多个泛型参数的函数,处理“嵌套”泛型类型。再次编辑以使其使用 Aggregate 函数:

static string GetFullName(Type t)
{
    if (!t.IsGenericType)
        return t.Name;
    StringBuilder sb=new StringBuilder();

    sb.Append(t.Name.Substring(0, t.Name.LastIndexOf("`")));
    sb.Append(t.GetGenericArguments().Aggregate("<",

        delegate(string aggregate,Type type)
            {
                return aggregate + (aggregate == "<" ? "" : ",") + GetFullName(type);
            }  
        ));
    sb.Append(">");

    return sb.ToString();
}

评论

0赞 Stefan Steinegger 10/8/2009
应该增强一点。泛型参数的格式应相同,它可以再次成为泛型类型。当然,它应该支持许多通用参数。
0赞 Erich 10/8/2009
我正在输入一个更复杂的版本,该版本处理了我刚刚发布的多个版本。
0赞 Erich 10/8/2009
再次编辑以使用聚合。如果您想要“旧”版本,请查看编辑历史记录。功能是相同的,但我想弄清楚聚合是如何工作的,这是学习:)的好方法
1赞 Robert Giesecke 10/8/2009
当您已经使用了 stringbuilder 时,您也可以在聚合调用中使用它:sb。Append(t.Name.Substring(0, t.Name.LastIndexOf(“'”)));int i = 0 t.GetGenericArguments() 。聚合(sb, (a, type) => a.Append((i++ == 0 ?"<" : ",") .Append(GetFullName(类型)));
0赞 ToolmakerSteve 8/7/2017
请参阅下面 CaringDev 的更简单答案
3赞 Robert Giesecke 10/8/2009 #3

这并不难。;-)

好吧,我会咬...g 下面的那个是隐蔽的,并显示没有命名空间的原始类型(就像 OP 写的那样):

  static string PrettyPrintGenericTypeName(Type typeRef)
  {
     var rootType = typeRef.IsGenericType
        ? typeRef.GetGenericTypeDefinition()
        : typeRef;

     var cleanedName = rootType.IsPrimitive
                          ? rootType.Name
                          : rootType.ToString();

     if (!typeRef.IsGenericType)
        return cleanedName;
     else
        return cleanedName.Substring(0,
                                     cleanedName.LastIndexOf('`'))
            + typeRef.GetGenericArguments()
                     .Aggregate("<",
                                (r, i) =>
                                   r
                                   + (r != "<" ? ", " : null)
                                   + PrettyPrintGenericTypeName(i))
            + ">";
  }

生成的 cleanedName 如下所示:System.Collections.Generic.Dictionary<System.Collections.Generic.List<Int32>, ConsoleApplication2.Program+SomeType>

19赞 CaringDev 8/13/2014 #4

使用内置函数和 Linq,可以编写

static string PrettyTypeName(Type t)
{
    if (t.IsArray)
    {
        return PrettyTypeName(t.GetElementType()) + "[]";
    }

    if (t.IsGenericType)
    {
        return string.Format(
            "{0}<{1}>",
            t.Name.Substring(0, t.Name.LastIndexOf("`", StringComparison.InvariantCulture)),
            string.Join(", ", t.GetGenericArguments().Select(PrettyTypeName)));
    }

    return t.Name;
}

注意:在 C# 4.0 之前的版本中,需要显式:string.Join.ToArray()

string.Join(", ", t.GetGenericArguments().Select(PrettyTypeName).ToArray()));

评论

0赞 ToolmakerSteve 8/7/2017
在带有 .NET 3.5 的 VS 2010 中(不确定哪个 C# 版本),出现编译器错误“无法从'System.Collections.Generic.IEnumerable<string>'转换为'string[]'” 修复:附加 .添加答案作为替代方案。t.GetGenericArguments().Select(PrettyTypeName).ToArray()
1赞 Motlicek Petr 11/4/2019
不适用于 . 必须添加。PrettyTypeName(typeof(ICollection<Tuple<string, int, Tuple<ICollection<string>,IList>[]>[]>))if (t.IsArray) return PrettyTypeName(t.GetElementType()) + "[]";
0赞 mamidon 5/26/2016 #5

另一个例子是我在这里绊倒之前自己写的。

    private string PrettyPrintGenericTypeName(Type p)
    {
        if (p.IsGenericType) {
            var simpleName = p.Name.Substring(0, p.Name.IndexOf('`'));
            var genericTypeParams = p.GenericTypeArguments.Select(PrettyPrintGenericTypeName).ToList();
            return string.Format("{0}<{1}>", simpleName, string.Join(", ", genericTypeParams));
        } else {
            return p.Name;
        }
    }
1赞 Amry 12/14/2016 #6

老问题,但我今天只需要这个。因此,我编写了一个扩展方法,它可以输出美观的 C# 格式的泛型名称,该名称可以处理多级嵌套泛型类型。

using System;
using System.Text;

public static class TypeExtensions
{
    public static string GetNiceName(this Type type, bool useFullName = false)
    {
        if (!type.IsGenericType) {
            return type.Name;
        }

        var typeNameBuilder = new StringBuilder();
        GetNiceGenericName(typeNameBuilder, type, useFullName);
        return typeNameBuilder.ToString();
    }

    static void GetNiceGenericName(StringBuilder sb, Type type, bool useFullName)
    {
        if (!type.IsGenericType) {
            sb.Append(useFullName ? type.FullName : type.Name);
            return;
        }

        var typeDef = type.GetGenericTypeDefinition();
        var typeName = useFullName ? typeDef.FullName : typeDef.Name;
        sb.Append(typeName);
        sb.Length -= typeName.Length - typeName.LastIndexOf('`');
        sb.Append('<');
        foreach (var typeArgument in type.GenericTypeArguments) {
            GetNiceGenericName(sb, typeArgument, useFullName);
            sb.Append(", ");
        }
        sb.Length -= 2;
        sb.Append('>');
    }
}
1赞 Ken Kin 3/8/2019 #7

如果不删除命名空间名称,只需说:

Regex.Replace(""+@type, @"`\d+\[", "<").Replace("]", ">");

如果你这样做了,请说:

Regex.Replace(Regex.Replace(""+@type, @"`\d+\[", "<").Replace("]", ">"), @"\w+\.", "")
0赞 CeeJay 11/17/2023 #8

几天前我遇到了同样的问题,我已经编写了自己的扩展方法。

此代码适用于普通类型和“可为 null 的引用类型”。

我不是 100% 舒尔,它在所有情况下都能正常工作,但像这样的类型工作正常:

public IDictionary<MyClass?[][], Dictionary<object?,int?[,]?>>? Dummy { get; set; }

这几乎完美地满足了我的需求:

public static class ReflectionExtensions
{
    public static string ToReadableTypeString(this PropertyInfo property, bool useFullnames = false, bool useInternalTypeNames= true)
    {
        return ToReadableTypeStringInternal(property.PropertyType, new NullabilityInfoContext().Create(property), useFullnames, useInternalTypeNames);
    }

    public static string ToReadableTypeString(this Type type, NullabilityInfo? nullabilityInfo = null, bool useFullnames = false, bool useInternalTypeNames= true)
    {
        return ToReadableTypeStringInternal(type, nullabilityInfo, useFullnames, useInternalTypeNames);
    }

    private static string ToReadableTypeStringInternal(Type type,NullabilityInfo? nullabilityInfo, bool useFullnames, bool useInternalTypeNames)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }

        string suffix;

        var underlyingType = Nullable.GetUnderlyingType(type);

        if (underlyingType != null)
        {
            type = underlyingType;
            suffix = "?";
        }
        else
        {
            suffix = nullabilityInfo?.ReadState == NullabilityState.Nullable ? "?" : "";
        }

        if (useInternalTypeNames)
        {
            if (type == typeof(string)) { return "string"+suffix; }
            if (type == typeof(sbyte)) { return "sbyte"+suffix; }
            if (type == typeof(byte)) { return "byte"+suffix; }
            if (type == typeof(short)) { return "short"+suffix; }
            if (type == typeof(ushort)) { return "ushort"+suffix; } 
            if (type == typeof(int  )) { return "int"+suffix; }
            if (type == typeof(uint)) { return "uint"+suffix; }
            if (type == typeof(long)) { return "long"+suffix; }
            if (type == typeof(ulong )) { return "ulong"+suffix; }
            if (type == typeof(nint )) { return "nint"+suffix; }
            if (type == typeof(nuint)) { return "nuint"+suffix; }
            if (type == typeof(float)) { return "float"+suffix; }
            if (type == typeof(double)) { return "double"+suffix; }
            if (type == typeof(decimal)) { return "decimal"+suffix; }
            if (type == typeof(bool)) { return "bool"+suffix; }
            if (type == typeof(char)) { return "char"+suffix; }
            if (type == typeof(object)) { return "object"+suffix; }
        }
        
        if (type.IsArray)
        {
            var sb = new StringBuilder(ToReadableTypeStringInternal(type.GetElementType()!, nullabilityInfo?.ElementType, useFullnames, useInternalTypeNames));

            if (type.IsVariableBoundArray)
            {
                sb.Append('[');
                sb.Append(new string(',',type.GetArrayRank()-1));
                sb.Append(']');
            }
            else
            {
                for (var i = 0; i < type.GetArrayRank(); i++)
                {
                    sb.Append("[]");
                }
            }
            
            return sb+suffix;
        }
        
        if (type.IsGenericType)
        {
            return (useFullnames?(type.FullName??type.Name):type.Name).Split("`").First() + "<" + string.Join(",", type.GenericTypeArguments.Select((type1,index) => ToReadableTypeStringInternal(type1,nullabilityInfo?.GenericTypeArguments[index], useFullnames, useInternalTypeNames))) + ">";
        }

        return (useFullnames?(type.FullName??type.Name):type.Name)+suffix;
    }
}