【问题标题】:Given a type instance, how to get generic type name in C#?给定一个类型实例,如何在 C# 中获取泛型类型名称?
【发布时间】:2011-01-27 18:34:48
【问题描述】:

给定一个泛型类型,包括

List<string>
Nullable<Int32>

如何获得 C# 的通用名称?

var t = typeof(Nullable<DateTime>);    
var s = t.GetGenericTypeDefinition().Name + "<" + t.GetGenericArguments()[0].Name + ">";

这会产生"Nullable`1&lt;DateTime&gt;",但我需要"Nullable&lt;DateTime&gt;"

【问题讨论】:

    标签: c# reflection code-generation


    【解决方案1】:

    我看到你已经接受了一个答案,但老实说,如果你只是将其中的内容与你已经写的内容结合起来,那么这个答案将不足以可靠地做到这一点。它在正确的轨道上,但是您的代码仅适用于只有一个泛型参数的泛型类型,并且仅在泛型类型参数本身不是泛型时才有效!

    这是一个在所有情况下都应该实际工作的函数(作为扩展方法编写):

    public static class TypeExtensions
    {
        public static string ToGenericTypeString(this Type t)
        {
            if (!t.IsGenericType)
                return t.Name;
            string genericTypeName = t.GetGenericTypeDefinition().Name;
            genericTypeName = genericTypeName.Substring(0,
                genericTypeName.IndexOf('`'));
            string genericArgs = string.Join(",",
                t.GetGenericArguments()
                    .Select(ta => ToGenericTypeString(ta)).ToArray());
            return genericTypeName + "<" + genericArgs + ">";
        }
    }
    

    此函数是递归且安全的。如果你在这个输入上运行它:

    Console.WriteLine(
        typeof(Dictionary<string, List<Func<string, bool>>>)
        .ToGenericTypeString());
    

    你得到这个(正确的)输出:

    Dictionary<String,List<Func<String,Boolean>>>
    

    【讨论】:

    • 可惜CLR没有这个功能。
    • @Paul Ruane:我同意它在某些情况下可能很有用,但是,CLR 与语言无关,并且没有办法实现这样的东西以同样好地工作在 C#、VB.NET、F#、IronPython 和所有其他 CLR 语言中。带有反引号的怪异名称实际上是真正的 CLR 类型名称;上面的格式是 C# 特定的。
    • Microsoft.CSharp 命名空间中可能至少有一个帮助函数。该命名空间包含特定于语言的类。
    • 虽然这个答案真的很棒,但在处理嵌套类或通用对象数组时它不起作用,请参阅我的答案以获得完整的解决方案
    【解决方案2】:

    虽然接受的解决方案仅适用于名称或非嵌套全名(通过将名称替换为全名,如@Ose E 的答案),但对于嵌套类型它仍然不起作用,也不适用于数组泛型类型。

    所以这里有一个可行的解决方案,(但请注意,这个解决方案只会设置实际参数,只有在设置了所有参数的情况下,换句话说,即使声明类型提供了类型争论,只要最里面的泛型类型没有,即使是基类也不会出现)。

        public static string ToGenericTypeString(this Type t, params Type[] arg)
        {
            if (t.IsGenericParameter || t.FullName == null) return t.Name;//Generic argument stub
            bool isGeneric = t.IsGenericType || t.FullName.IndexOf('`') >= 0;//an array of generic types is not considered a generic type although it still have the genetic notation
            bool isArray = !t.IsGenericType && t.FullName.IndexOf('`') >= 0;
            Type genericType = t;
            while (genericType.IsNested && genericType.DeclaringType.GetGenericArguments().Count()==t.GetGenericArguments().Count())//Non generic class in a generic class is also considered in Type as being generic
            {
                genericType = genericType.DeclaringType;
            }
            if (!isGeneric) return t.FullName.Replace('+', '.');
    
            var arguments = arg.Any() ? arg : t.GetGenericArguments();//if arg has any then we are in the recursive part, note that we always must take arguments from t, since only t (the last one) will actually have the constructed type arguments and all others will just contain the generic parameters
            string genericTypeName = genericType.FullName;
            if (genericType.IsNested)
            {
                var argumentsToPass = arguments.Take(genericType.DeclaringType.GetGenericArguments().Count()).ToArray();//Only the innermost will return the actual object and only from the GetGenericArguments directly on the type, not on the on genericDfintion, and only when all parameters including of the innermost are set
                arguments = arguments.Skip(argumentsToPass.Count()).ToArray();
                genericTypeName = genericType.DeclaringType.ToGenericTypeString(argumentsToPass) + "." + genericType.Name;//Recursive
            }
            if (isArray)
            {
                genericTypeName = t.GetElementType().ToGenericTypeString() + "[]";//this should work even for multidimensional arrays
            }
            if (genericTypeName.IndexOf('`') >= 0)
            {
                genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
                string genericArgs = string.Join(",", arguments.Select(a => a.ToGenericTypeString()).ToArray());
                    //Recursive
                genericTypeName = genericTypeName + "<" + genericArgs + ">";
                if (isArray) genericTypeName += "[]";
            }
            if (t != genericType)
            {
                genericTypeName += t.FullName.Replace(genericType.FullName, "").Replace('+','.');
            }
            if (genericTypeName.IndexOf("[") >= 0 && genericTypeName.IndexOf("]") != genericTypeName.IndexOf("[") +1) genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf("["));//For a non generic class nested in a generic class we will still have the type parameters at the end 
            return genericTypeName;
        }
    

    【讨论】:

      【解决方案3】:

      这将产生与 cs 代码生成器完全相同的代码结果。 我已经改进了 yoel halb 的代码。

      /// <summary>
          ///     Gets the CS Type Code for a type
          /// </summary>
          /// <param name="type">The type.</param>
          /// <returns></returns>
          /// <exception cref="System.ArgumentNullException">type</exception>
          public static string GetCSTypeName(this Type type)
          {
              if (type == typeof(string))
              {
                  return "string";
              }
              else if (type == typeof(object)) { return "object"; }
              else if (type == typeof(bool)) { return "bool"; }
              else if (type == typeof(char)) { return "char"; }
              else if (type == typeof(int)) { return "int"; }
              else if (type == typeof(float)) { return "float"; }
              else if (type == typeof(double)) { return "double"; }
              else if (type == typeof(long)) { return "long"; }
              else if (type == typeof(ulong)) { return "ulong"; }
              else if (type == typeof(uint)) { return "uint"; }
              else if (type == typeof(byte)) { return "byte"; }
              else if (type == typeof(Int64)) { return "Int64"; }
              else if (type == typeof(short)) { return "short"; }
              else if (type == typeof(decimal)) { return "decimal"; }
              else if (type.IsGenericType)
              {
                  return $"{ToGenericTypeString(type)}";
              }
              else if (type.IsArray)
              {
                  List<string> arrayLength = new List<string>();
                  for (int i = 0; i < type.GetArrayRank(); i++)
                  {
                      arrayLength.Add("[]");
                  }
                  return GetCSTypeName(type.GetElementType()) + string.Join("", arrayLength).Replace("+", ".");
              }
              else
              {
                  return type.FullName.Replace("+", ".");
              }
          }
      
          private static string ToCSReservatedWord(this Type type, bool fullName)
          {
              if (type == typeof(string))
              {
                  return "string";
              }
              else if (type == typeof(object)) { return "object"; }
              else if (type == typeof(bool)) { return "bool"; }
              else if (type == typeof(char)) { return "char"; }
              else if (type == typeof(int)) { return "int"; }
              else if (type == typeof(float)) { return "float"; }
              else if (type == typeof(double)) { return "double"; }
              else if (type == typeof(long)) { return "long"; }
              else if (type == typeof(ulong)) { return "ulong"; }
              else if (type == typeof(uint)) { return "uint"; }
              else if (type == typeof(byte)) { return "byte"; }
              else if (type == typeof(Int64)) { return "Int64"; }
              else if (type == typeof(short)) { return "short"; }
              else if (type == typeof(decimal)) { return "decimal"; }
              else
              {
                  if (fullName)
                  {
                      return type.FullName;
                  }
                  else
                  {
                      return type.Name;
                  }
      
              }
          }
      
          public static string ToGenericTypeString(this Type t, params Type[] arg)
          {
              if (t.IsGenericParameter || t.FullName == null) return t.FullName;//Generic argument stub
              bool isGeneric = t.IsGenericType || t.FullName.IndexOf('`') >= 0;//an array of generic types is not considered a generic type although it still have the genetic notation
              bool isArray = !t.IsGenericType && t.FullName.IndexOf('`') >= 0;
              Type genericType = t;
              while (genericType.IsNested && genericType.DeclaringType.GetGenericArguments().Count() == t.GetGenericArguments().Count())//Non generic class in a generic class is also considered in Type as being generic
              {
                  genericType = genericType.DeclaringType;
              }
              if (!isGeneric) return ToCSReservatedWord(t, true).Replace('+', '.');
      
              var arguments = arg.Any() ? arg : t.GetGenericArguments();//if arg has any then we are in the recursive part, note that we always must take arguments from t, since only t (the last one) will actually have the constructed type arguments and all others will just contain the generic parameters
              string genericTypeName = genericType.ToCSReservatedWord(true);
              if (genericType.IsNested)
              {
                  var argumentsToPass = arguments.Take(genericType.DeclaringType.GetGenericArguments().Count()).ToArray();//Only the innermost will return the actual object and only from the GetGenericArguments directly on the type, not on the on genericDfintion, and only when all parameters including of the innermost are set
                  arguments = arguments.Skip(argumentsToPass.Count()).ToArray();
                  genericTypeName = genericType.DeclaringType.ToGenericTypeString(argumentsToPass) + "." + ToCSReservatedWord(genericType, false);//Recursive
              }
              if (isArray)
              {
                  genericTypeName = t.GetElementType().ToGenericTypeString() + "[]";//this should work even for multidimensional arrays
              }
              if (genericTypeName.IndexOf('`') >= 0)
              {
                  genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`'));
                  string genericArgs = string.Join(", ", arguments.Select(a => a.ToGenericTypeString()).ToArray());
                  //Recursive
                  genericTypeName = genericTypeName + "<" + genericArgs + ">";
                  if (isArray) genericTypeName += "[]";
              }
              if (t != genericType)
              {
                  genericTypeName += t.FullName.Replace(genericType.ToCSReservatedWord(true), "").Replace('+', '.');
              }
              if (genericTypeName.IndexOf("[") >= 0 && genericTypeName.IndexOf("]") != genericTypeName.IndexOf("[") + 1) genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf("["));//For a non generic class nested in a generic class we will still have the type parameters at the end 
              return genericTypeName;
          }
      

      这将按预期通过以下单元测试。

      [TestClass]
      public class GetCSName
      {
      
          private string GetCSCompilerName(Type type)
          {
              if (type == null)
              {
                  throw new ArgumentNullException(nameof(type));
              }
              var compiler = new CSharpCodeProvider();
              var typeRef = new CodeTypeReference(type);
              return compiler.GetTypeOutput(typeRef);
          }
      
          [TestMethod]
          public void TestMethod1()
          {
              List<Type> typesToTest = new List<Type>();
              typesToTest.Add(typeof(string));
              typesToTest.Add(typeof(string[]));
              typesToTest.Add(typeof(object[]));
              typesToTest.Add(typeof(bool[]));
              typesToTest.Add(typeof(string));
              typesToTest.Add(typeof(object));
              typesToTest.Add(typeof(int));
              typesToTest.Add(typeof(double));
              typesToTest.Add(typeof(float));
              typesToTest.Add(typeof(bool));
              typesToTest.Add(typeof(char));
              typesToTest.Add(typeof(decimal));
              typesToTest.Add(typeof(decimal?[]));
              typesToTest.Add(typeof(decimal?[][]));
              typesToTest.Add(typeof(Int64));
              typesToTest.Add(typeof(Guid));
              typesToTest.Add(typeof(int?));
              typesToTest.Add(typeof(double?));
              typesToTest.Add(typeof(float?));
              typesToTest.Add(typeof(bool?));
              typesToTest.Add(typeof(char?));
              typesToTest.Add(typeof(decimal?));
              typesToTest.Add(typeof(Int64?));
              typesToTest.Add(typeof(Guid?));
              typesToTest.Add(typeof(List<string>));
              typesToTest.Add(typeof(Dictionary<string, Guid>));
              typesToTest.Add(typeof(Dictionary<string, Guid>[]));
              typesToTest.Add(typeof(Dictionary<string, Guid?>));
              typesToTest.Add(typeof(Dictionary<string, Dictionary<string, Guid?>>));
              typesToTest.Add(typeof(Dictionary<string, Dictionary<string, Guid?>>[]));
              typesToTest.Add(typeof(Dictionary<string, Dictionary<string, Guid?>>[][]));
              typesToTest.Add(typeof(int[]));
              typesToTest.Add(typeof(int[][]));
              typesToTest.Add(typeof(int[][][]));
              typesToTest.Add(typeof(int[][][][]));
              typesToTest.Add(typeof(int[][][][][]));
              typesToTest.Add(typeof(TestClass));
              typesToTest.Add(typeof(List<TestClass>));
              typesToTest.Add(typeof(Dictionary<TestClass, TestClass>));
              typesToTest.Add(typeof(Dictionary<string, TestClass>));
              typesToTest.Add(typeof(List<Dictionary<string, TestClass>>));
              typesToTest.Add(typeof(List<Dictionary<string, GenericTestClass<string>>>));
              typesToTest.Add(typeof(GenericTestClass<string>.SecondSubType<decimal>));
              typesToTest.Add(typeof(GenericTestClass<string>.SecondSubType));
              typesToTest.Add(typeof(GenericTestClass<string, int>.SecondSubType));
              typesToTest.Add(typeof(GenericTestClass<string, Dictionary<string,int>>.SecondSubType<string>));
              typesToTest.Add(typeof(GenericTestClass<string, Dictionary<string, int>>.SecondSubType<GenericTestClass<string, Dictionary<string, int>>>));
      
      
              foreach (var t in typesToTest)
              {
                  if (GetCSCompilerName(t) != t.GetCSTypeName())
                  {
                      Console.WriteLine($"FullName:\r\n{t.FullName}");
                      Console.WriteLine("C " + GetCSCompilerName(t));
                      Console.WriteLine("R " + t.GetCSTypeName());
                      Console.WriteLine("Equal: " + (GetCSCompilerName(t) == t.GetCSTypeName()));
                      Console.WriteLine();
      
                      Assert.Fail($"From CSharpCodeProvider '{GetCSCompilerName(t)}' is not equal to {t.GetCSTypeName()}");
                  }
                  else
                  {
                      Console.WriteLine($"Passed: {t.GetCSTypeName()}");
                      //ignore because of equal.
                  }
      
      
              }
      
          }
      
          public class TestClass
          {
      
          }
      
          public class GenericTestClass<T>
          {
              public class SecondSubType
              {
      
              }
      
              public class SecondSubType<T2>
              {
      
              }
          }
      
          public class GenericTestClass<T1,T2>
          {
              public class SecondSubType
              {
      
              }
      
              public class SecondSubType<T2>
              {
      
              }
          }
      }
      

      结果将是:

      Passed: string
      Passed: string[]
      Passed: object[]
      Passed: bool[]
      Passed: string
      Passed: object
      Passed: int
      Passed: double
      Passed: float
      Passed: bool
      Passed: char
      Passed: decimal
      Passed: System.Nullable<decimal>[]
      Passed: System.Nullable<decimal>[][]
      Passed: long
      Passed: System.Guid
      Passed: System.Nullable<int>
      Passed: System.Nullable<double>
      Passed: System.Nullable<float>
      Passed: System.Nullable<bool>
      Passed: System.Nullable<char>
      Passed: System.Nullable<decimal>
      Passed: System.Nullable<long>
      Passed: System.Nullable<System.Guid>
      Passed: System.Collections.Generic.List<string>
      Passed: System.Collections.Generic.Dictionary<string, System.Guid>
      Passed: System.Collections.Generic.Dictionary<string, System.Guid>[]
      Passed: System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>>
      Passed: System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>>>
      Passed: System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>>>[]
      Passed: System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<string, System.Nullable<System.Guid>>>[][]
      Passed: int[]
      Passed: int[][]
      Passed: int[][][]
      Passed: int[][][][]
      Passed: int[][][][][]
      Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass
      Passed: System.Collections.Generic.List<Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass>
      Passed: System.Collections.Generic.Dictionary<Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass, Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass>
      Passed: System.Collections.Generic.Dictionary<string, Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass>
      Passed: System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, Eneon.Common.Utils.Extensions.Tests.GetCSName.TestClass>>
      Passed: System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string>>>
      Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string>.SecondSubType<decimal>
      Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string>.SecondSubType
      Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, int>.SecondSubType
      Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, System.Collections.Generic.Dictionary<string, int>>.SecondSubType<string>
      Passed: Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, System.Collections.Generic.Dictionary<string, int>>.SecondSubType<Eneon.Common.Utils.Extensions.Tests.GetCSName.GenericTestClass<string, System.Collections.Generic.Dictionary<string, int>>>
      

      【讨论】:

        【解决方案4】:

        这是我的解决方案,它也适用于嵌套类和泛型:

            public static string GenericTypeString(this Type t)
            {
                if (!t.IsGenericType)
                {
                    return t.GetFullNameWithoutNamespace()
                            .ReplacePlusWithDotInNestedTypeName();
                }
        
                return t.GetGenericTypeDefinition()
                        .GetFullNameWithoutNamespace()
                        .ReplacePlusWithDotInNestedTypeName()
                        .ReplaceGenericParametersInGenericTypeName(t);
            }
        
            private static string GetFullNameWithoutNamespace(this Type type)
            {
                if (type.IsGenericParameter)
                {
                    return type.Name;
                }
        
                const int dotLength = 1;
                return type.FullName.Substring(type.Namespace.Length + dotLength);
            }
        
            private static string ReplacePlusWithDotInNestedTypeName(this string typeName)
            {
                return typeName.Replace('+', '.');
            }
        
            private static string ReplaceGenericParametersInGenericTypeName(this string typeName, Type t)
            {
                var genericArguments = t.GetGenericArguments();
        
                const string regexForGenericArguments = @"`[1-9]\d*";
        
                var rgx = new Regex(regexForGenericArguments);
        
                typeName = rgx.Replace(typeName, match =>
                {
                    var currentGenericArgumentNumbers = int.Parse(match.Value.Substring(1));
                    var currentArguments = string.Join(",", genericArguments.Take(currentGenericArgumentNumbers).Select(GenericTypeString));
                    genericArguments = genericArguments.Skip(currentGenericArgumentNumbers).ToArray();
                    return string.Concat("<", currentArguments, ">");
                });
        
                return typeName;
            }
        

        【讨论】:

          【解决方案5】:

          对@Aaronaught 的轻微补充

          public string ToGenericTypeString(Type t)
          {
              if (!t.IsGenericType)
                  return t.FullName;
              string genericTypeName = t.GetGenericTypeDefinition().FullName;
              genericTypeName = genericTypeName.Substring(0,
                  genericTypeName.IndexOf('`'));
              string genericArgs = string.Join(",",
                  t.GetGenericArguments()
                      .Select(ta => ToGenericTypeString(ta)).ToArray());
              return genericTypeName + "<" + genericArgs + ">";
          }
          

          【讨论】:

          • 看起来你唯一改变的就是从NameFullName?我不确定这是否足以证明替代答案的实质性变化 - 它可能应该作为评论发布。
          • 这不是替代品。全名有助于命名空间解析。
          猜你喜欢
          • 1970-01-01
          • 2019-01-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多