【问题标题】:C# Get Generic Type NameC# 获取通用类型名称
【发布时间】:2010-11-15 14:54:26
【问题描述】:

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

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

我想要的是弹出一个带有List 的消息框,显示...我该怎么做?

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    你可以实现一个扩展方法来获取一个类型的“友好名称”,像这样:

    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”。

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

    命名空间、standard aliases for built-in types 和 StringBuilder 的使用留给读者作为练习。 ;-)

    【讨论】:

    • 有了新的 C# 可爱,您现在可以在反引号检查之后写下所有的位(尽管有点长),这也将处理嵌套泛型:friendlyName += $"&lt;{string.Join(",", type.GetGenericArguments().Select(p =&gt; type.GetFriendlyName()))}&gt;"
    • 您可能希望对嵌套的泛型类型使用递归:string typeParamName = typeParameters[i].GetFriendlyName();
    • 请注意,这不适用于泛型类型的数组,它们的名称例如 MyType`1[],因此数组尾括号 [] 将被 friendlyName.Remove(iBacktick); 删除。您需要使用type.IsArray 对此进行测试,并使用type.GetArrayRank() 确定数组有多少维。
    • @joshcomley 如果你在Select 中使用type 而不是p,你会让堆栈不愉快。 ;)
    【解决方案2】:
    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"
    }
    

    【讨论】:

    • 如果你需要像List&lt;T&gt;这样的泛型类型T,你可以使用t.GetGenericArguments()[0].Name这样的东西。不久前我需要这个,但在任何地方都找不到。如果您有List&lt;string&gt;,这将返回string
    • 如果您在反引号之后删除所有内容,您会丢失任何可能存在的数组括号。
    【解决方案3】:

    我对 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());
        }
    }
    

    【讨论】:

    • 这可以编译吗?我不得不稍微调整一下。已编辑以进行更正。
    • 你忘记了 Nullable。要美化可空值,您应该使用如下内容:if (type.GetGenericTypeDefinition() == typeof(Nullable&lt;&gt;)) return type.GetGenericArguments().First().GetFriendlyName() + "?";
    • 这与 yoyo 的答案有相同的问题,因为它不检查数组并从名称中删除任何 [] 括号。 GetFriendlyName(typeof(Foo&lt;Bar&lt;Bas&gt;[]&gt;)) 返回Foo&lt;Bar&lt;Bas&gt;&gt;
    • 别名也不处理数组;使用 type.IsArraytype.GetElementType() 解析数组的别名。我更新了我的回答 about type aliases 以展示如何做到这一点。
    【解决方案4】:

    假设您只想看到它的 List&lt;T&gt; 而不是 List&lt;string&gt; 您需要这样做:

    MessageBox.Show(t.GetGenericTypeDefinition().FullName)
    

    http://msdn.microsoft.com/en-us/library/system.type.getgenerictypedefinition.aspx

    【讨论】:

      【解决方案5】:
      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;
          }
      }
      

      【讨论】:

        【解决方案6】:

        这是我对此的看法。我没有放反引号检查,因为就我所看到的而言,它总是在那里。您可以根据需要添加它,但我喜欢保持简单。

        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;
            }
        }
        

        【讨论】:

          【解决方案7】:

          我知道这是一个老问题,但我和一位同事需要为一些智能感知/罗斯林工作做这个。最佳解决方案似乎是Ali's 解决方案,但它不适用于嵌套类型:

              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" | 43uS
          "listTest" | 3uS
          "dictTest" | 2uS
          "nestTest" | 5uS
          "superNestTest" | 9uS
          "superNestTest2" | 9uS
          在每个场景中执行上述代码 200 次后的平均次数

          【讨论】:

          • 这是最好的 IMO,应该是 .NET Standard/Core 中的标准方法。我将 type.FullName 和 type.AssemblyQualifiedName 和 type.ToString() 更改为一个简单的 type.Name ,因为我想要在 Roslyn 不可用的地方编写代码完成引擎的较短名称。
          【解决方案8】:

          这是基于之前支持别名(包括 Nullable)和数组的答案的完整实现:​​

          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&lt;string[,], List&lt;int?[,][]&gt;[,,]&gt;[]"


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

          【讨论】:

          • (对于任何对 17:th “未定义” 特殊类型感到好奇的人,它最初是为另一种时间相关类型保留的。它仅作为早期内部 1.0 之前的 C# 草案的一部分存在,并且据我所知,从未公开发布过。)
          【解决方案9】:

          我已经改进了 yoyos 版本,以便在代码生成中使用。 请注意,所有类型现在都引用完全限定 => 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('+', '.');
                      }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-02-04
            • 2010-11-06
            • 1970-01-01
            • 2013-11-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多