C# 获取泛型类型名称

56

type.IsGenericType = true时,我需要一种获取类型名称的方法。

    Type t = typeof(List<String>);
    MessageBox.Show( ..?.. );

我想要的是一个弹出消息框,其中包含一个显示“List”内容的代码...我该怎么做?
11个回答

78
您可以实现一个扩展方法来获取类型的“友好名称”,如下所示:
public static class TypeNameExtensions
{
    public static string GetFriendlyName(this Type type)
    {
        string friendlyName = type.Name;
        if (type.IsGenericType)
        {
            int iBacktick = friendlyName.IndexOf('`');
            if (iBacktick > 0)
            {
                friendlyName = friendlyName.Remove(iBacktick);
            }
            friendlyName += "<";
            Type[] typeParameters = type.GetGenericArguments();
            for (int i = 0; i < typeParameters.Length; ++i)
            {
                string typeParamName = GetFriendlyName(typeParameters[i]);
                friendlyName += (i == 0 ? typeParamName : "," + typeParamName);
            }
            friendlyName += ">";
        }

        return friendlyName;
    }
}

在你的项目中加入这个,现在你可以说:
MessageBox.Show(t.GetFriendlyName());

它将显示"List<String>"。

我知道OP没有要求泛型类型参数,但我更喜欢这种方式。;-)

命名空间,内置类型的标准别名和使用StringBuilder留给读者练习。;-)


8
使用新的 C# 语法,你现在可以在反引号后的所有代码中写一行(虽然有些冗长),这也将处理嵌套泛型: friendlyName += $"<{string.Join(",", type.GetGenericArguments().Select(p => type.GetFriendlyName()))}>" 其中,type.GetGenericArguments() 返回泛型类型参数数组;Select() 对每个泛型类型参数应用 type.GetFriendlyName() 方法;string.Join() 使用逗号将结果连接成一个字符串。最终的结果会添加到 friendlyName 中。 - joshcomley
3
你可能希望在嵌套的泛型类型中使用递归:string typeParamName = typeParameters[i].GetFriendlyName(); - PeterB
请注意,这不适用于通用类型的数组,它们的名称为MyType`1[],因此将使用friendlyName.Remove(iBacktick);删除尾部数组括号[]。您需要使用type.IsArray进行测试,并使用type.GetArrayRank()确定数组具有多少维。 - AnorZaken
@joshcomley 如果你在 Select 中使用 type 而不是 p,那么你会让堆栈感到不高兴。 ;) - AnorZaken

44
Type t = ...;

if (t.IsGenericType)
{
    Type g = t.GetGenericTypeDefinition();

    MessageBox.Show(g.Name);                                // displays "List`1"

    MessageBox.Show(g.Name.Remove(g.Name.IndexOf('`')));    // displays "List"
}

6
如果您需要获取泛型类型的类型T,例如 List<T>,可以使用类似于 t.GetGenericArguments()[0].Name 的代码。我以前有一次需要用到这个,但无法在任何地方找到它。如果您有一个 List<string> 类型,这将返回字符串 "string" - Felipe Correa
如果您删除反引号后面的所有内容,则会丢失可能存在的任何数组括号。 - AnorZaken

27

我对 yoyo 的方法的看法。它保证了基本类型更友好易懂的名称,处理了数组,并且递归处理嵌套的泛型。此外还有单元测试。

    private static readonly Dictionary<Type, string> _typeToFriendlyName = new Dictionary<Type, string>
    {
        { typeof(string), "string" },
        { typeof(object), "object" },
        { typeof(bool), "bool" },
        { typeof(byte), "byte" },
        { typeof(char), "char" },
        { typeof(decimal), "decimal" },
        { typeof(double), "double" },
        { typeof(short), "short" },
        { typeof(int), "int" },
        { typeof(long), "long" },
        { typeof(sbyte), "sbyte" },
        { typeof(float), "float" },
        { typeof(ushort), "ushort" },
        { typeof(uint), "uint" },
        { typeof(ulong), "ulong" },
        { typeof(void), "void" }
    };

    public static string GetFriendlyName(this Type type)
    {
        string friendlyName;
        if (_typeToFriendlyName.TryGetValue(type, out friendlyName))
        {
            return friendlyName;
        }

        friendlyName = type.Name;
        if (type.IsGenericType)
        {
            int backtick = friendlyName.IndexOf('`');
            if (backtick > 0)
            {
                friendlyName = friendlyName.Remove(backtick);
            }
            friendlyName += "<";
            Type[] typeParameters = type.GetGenericArguments();
            for (int i = 0; i < typeParameters.Length; i++)
            {
                string typeParamName = typeParameters[i].GetFriendlyName();
                friendlyName += (i == 0 ? typeParamName : ", " + typeParamName);
            }
            friendlyName += ">";
        }

        if (type.IsArray)
        {
            return type.GetElementType().GetFriendlyName() + "[]";
        }

        return friendlyName;
    }

[TestFixture]
public class TypeHelperTest
{
    [Test]
    public void TestGetFriendlyName()
    {
        Assert.AreEqual("string", typeof(string).FriendlyName());
        Assert.AreEqual("int[]", typeof(int[]).FriendlyName());
        Assert.AreEqual("int[][]", typeof(int[][]).FriendlyName());
        Assert.AreEqual("KeyValuePair<int, string>", typeof(KeyValuePair<int, string>).FriendlyName());
        Assert.AreEqual("Tuple<int, string>", typeof(Tuple<int, string>).FriendlyName());
        Assert.AreEqual("Tuple<KeyValuePair<object, long>, string>", typeof(Tuple<KeyValuePair<object, long>, string>).FriendlyName());
        Assert.AreEqual("List<Tuple<int, string>>", typeof(List<Tuple<int, string>>).FriendlyName());
        Assert.AreEqual("Tuple<short[], string>", typeof(Tuple<short[], string>).FriendlyName());
    }
}

这个能编译吗?我稍微调整了一下。已编辑进行更正。 - Humberto
4
你忘记了Nullable。为了使可空类型更美观,你应该使用类似于以下代码的方式: if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) return type.GetGenericArguments().First().GetFriendlyName() + "?"; - Igor S
这个代码与yoyo的回答有相同的问题,即它没有检查数组并从名称中去掉任何[]括号。 GetFriendlyName(typeof(Foo<Bar<Bas>[]>)) 返回 Foo<Bar<Bas>> - AnorZaken
别名处理也不支持数组;使用 type.IsArraytype.GetElementType() 来解决数组的别名。我已经更新了我的答案,展示了如何做到这一点 有关类型别名的问题 - AnorZaken

10

10
public static class TypeNameExtensions
{
    public static string GetFriendlyName(this Type type)
    {
        var friendlyName = type.Name;
        if (!type.IsGenericType) return friendlyName;

        var iBacktick = friendlyName.IndexOf('`');
        if (iBacktick > 0) friendlyName = friendlyName.Remove(iBacktick);

        var genericParameters = type.GetGenericArguments().Select(x => x.GetFriendlyName());
        friendlyName += "<" + string.Join(", ", genericParameters) + ">";

        return friendlyName;
    }
}

7

这是我的看法。我没有加上反引号检查,因为我的观察是它总是存在的。如果你想要添加它,可以这样做,但我喜欢保持简单。

public static string GetFriendlyName(this Type type)
{
    if (type.IsGenericType)
    {
        var name = type.Name.Substring(0, type.Name.IndexOf('`'));
        var types = string.Join(",", type.GetGenericArguments().Select(GetFriendlyName));
        return $"{name}<{types}>";
    }
    else
    {
        return type.Name;
    }
}

6

我知道这是一个老问题,但我和同事需要为一些智能感知/罗斯林工作做到这一点。最优解似乎是Ali提供的方案,但它对于嵌套类型不起作用:

    int i = 1; //would work
    List<string> listTest = new List<string>(); //would work
    Dictionary<string, int> dictTest = new Dictionary<string, int>(); //would work
    Dictionary<int, List<string>> nestTest = new Dictionary<int, List<string>>(); //would fail
    Dictionary<int, List<Dictionary<string, List<object>>>> superNestTest = new Dictionary<int, List<Dictionary<string, List<object>>>>(); //would fail
    Dictionary<int, List<Dictionary<string, int>>> superNestTest2 = new Dictionary<int, List<Dictionary<string, int>>>(); //would fail

为了解决这些问题,我将该函数转换为递归方法:
public static class TypeExtensions
{
    public static string GetFriendlyName(this Type type)
    {
        string friendlyName = type.FullName;
        if (type.IsGenericType)
        {
            friendlyName = GetTypeString(type);
        }
        return friendlyName;
    }

    private static string GetTypeString(Type type)
    {
        var t = type.AssemblyQualifiedName;

        var output = new StringBuilder();
        List<string> typeStrings = new List<string>();  

        int iAssyBackTick = t.IndexOf('`') + 1;
        output.Append(t.Substring(0, iAssyBackTick - 1).Replace("[", string.Empty));
        var genericTypes = type.GetGenericArguments();

        foreach (var genType in genericTypes)
        {
            typeStrings.Add(genType.IsGenericType ? GetTypeString(genType) : genType.ToString());
        }

        output.Append($"<{string.Join(",", typeStrings)}>");
        return output.ToString();
    }
}

之前的示例/测试用例运行产生以下输出:

System.Int32
System.Collections.Generic.List<System.String>
System.Collections.Generic.Dictionary<System.String,System.Int32>
System.Collections.Generic.Dictionary<System.Int32,System.Collections.Generic.List<System.String>>
System.Collections.Generic.Dictionary<System.Int32,System.Collections.Generic.List<System.Collections.Generic.Dictionary<System.String,System.Collections.Generic.List<System.Object>>>>
System.Collections.Generic.Dictionary<System.Int32,System.Collections.Generic.List<System.Collections.Generic.Dictionary<System.String,System.Int32>>>

我花了一些时间来解决嵌套类型的问题,所以想在这里记录下来,以确保将来的任何人都可以节省相当多的时间(和头疼!)。我也已经检查了性能,并且完成时间为微秒级别(在最后一个场景中为8微秒):
性能结果 (变量名称使用原场景列表中的名称)
"i" | 43微秒 "listTest" | 3微秒 "dictTest" | 2微秒 "nestTest" | 5微秒 "superNestTest" | 9微秒 "superNestTest2" | 9微秒
在对每个场景执行上述代码200次之后的平均时间。

1
这是我认为最好的方法,应该成为.NET Standard/Core的标准方法。尽管我想要更短的名称用于我正在编写的代码完成引擎(Roslyn不可用),但我已经将type.FullName、type.AssemblyQualifiedName和type.ToString()替换为简单的type.Name。 - John Ernest

5

以下是基于之前答案的完整实现,支持别名(包括可空别名)和数组:

public static class TypeNameExtensions
{
    public static string GetFriendlyName(this Type type, bool aliasNullable = true, bool includeSpaceAfterComma = true)
    {
        TryGetInnerElementType(ref type, out string arrayBrackets);
        if (!TryGetNameAliasNonArray(type, out string friendlyName))
        {
            if (!type.IsGenericType)
            {
                friendlyName = type.Name;
            }
            else
            {
                if (aliasNullable && type.GetGenericTypeDefinition() == typeof(System.Nullable<>))
                {
                    string generics = GetFriendlyName(type.GetGenericArguments()[0]);
                    friendlyName = generics + "?";
                }
                else
                {
                    string generics = GetFriendlyGenericArguments(type, includeSpaceAfterComma);
                    int iBacktick = type.Name.IndexOf('`');
                    friendlyName = (iBacktick > 0 ? type.Name.Remove(iBacktick) : type.Name)
                        + $"<{generics}>";
                }
            }
        }
        return friendlyName + arrayBrackets;
    }

    public static bool TryGetNameAlias(this Type type, out string alias)
    {
        TryGetInnerElementType(ref type, out string arrayBrackets);
        if (!TryGetNameAliasNonArray(type, out alias))
            return false;
        alias += arrayBrackets;
        return true;
    }

    private static string GetFriendlyGenericArguments(Type type, bool includeSpaceAfterComma)
        => string.Join(
            includeSpaceAfterComma ? ", " : ",",
            type.GetGenericArguments().Select(t => t.GetFriendlyName())
            );

    private static bool TryGetNameAliasNonArray(Type type, out string alias)
        => (alias = TypeAliases[(int)Type.GetTypeCode(type)]) != null
        && !type.IsEnum;

    private static bool TryGetInnerElementType(ref Type type, out string arrayBrackets)
    {
        arrayBrackets = null;
        if (!type.IsArray)
            return false;
        do
        {
            arrayBrackets += "[" + new string(',', type.GetArrayRank() - 1) + "]";
            type = type.GetElementType();
        }
        while (type.IsArray);
        return true;
    }

    private static readonly string[] TypeAliases = {
        "void",     // 0
        null,       // 1 (any other type)
        "DBNull",   // 2
        "bool",     // 3
        "char",     // 4
        "sbyte",    // 5
        "byte",     // 6
        "short",    // 7
        "ushort",   // 8
        "int",      // 9
        "uint",     // 10
        "long",     // 11
        "ulong",    // 12
        "float",    // 13
        "double",   // 14
        "decimal",  // 15
        null,       // 16 (DateTime)
        null,       // 17 (-undefined-)
        "string",   // 18
    };
}

测试过各种无意义的内容,例如:

var type = typeof(Dictionary<string[,], List<int?[,][]>[,,]>[]);
var name = type.GetFriendlyName();
Console.WriteLine(name);

而且它确实会返回:"Dictionary<string[,], List<int?[,][]>[,,]>[]"


编辑:已更新以正确处理enum类型。


对于任何对于第17种"undefined"特殊类型感到好奇的人,它最初是为另一种与时间相关的类型保留的。它只存在于早期1.0版本之前的C#草稿中,并且据我所知从未出现在任何公共发布版本中。 - AnorZaken

3

我已经改进了yoyo版本用于代码生成,值得注意的是现在所有类型都是全限定名 => global::System.String.

            public static string GetFriendlyTypeName(Type type)
            {
                string friendlyName = type.Name;
                if (type.IsGenericType)
                {
                    int iBacktick = friendlyName.IndexOf('`');
                    if (iBacktick > 0)
                    {
                        friendlyName = friendlyName.Remove(iBacktick);
                    }
                    friendlyName += "<";
                    Type[] typeParameters = type.GetGenericArguments();
                    for (int i = 0; i < typeParameters.Length; ++i)
                    {
                        string typeParamName = GetFriendlyTypeName(typeParameters[i]);
                        friendlyName += (i == 0 ? typeParamName : "," + typeParamName);
                    }
                    friendlyName += ">";
                    friendlyName = "global::" + type.Namespace + "." + friendlyName;
                }
                else
                {
                    friendlyName = "global::" + type.FullName;
                }

                return friendlyName.Replace('+', '.');
            }

0
在最新版本的C#中,您可以使用以下代码: var s = x.GetType().ShortDisplayName(); 它会返回Thing<IFoo> 编辑:抱歉,这只适用于EF Core的扩展。 :(

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