34

Given a generic type, including

List<string>
Nullable<Int32>

How do I get a generic name for C#?

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

This yields "Nullable`1<DateTime>", but I need "Nullable<DateTime>".

svick
246k54 gold badges405 silver badges534 bronze badges
asked Mar 15, 2010 at 16:38

5 Answers 5

69

I see you already accepted an answer, but honestly, that answer isn't going to be enough to do this reliably if you just combine what's in there with what you already wrote. It's on the right track, but your code will only work for generic types with exactly one generic parameter, and it will only work when the generic type parameter itself is not generic!

This is a function (written as an extension method) that should actually work in all cases:

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

This function is recursive and safe. If you run it on this input:

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

You get this (correct) output:

Dictionary<String,List<Func<String,Boolean>>>
answered Mar 15, 2010 at 16:56
4
  • 3
    It's a pity the CLR does not come with this function. Commented Mar 15, 2010 at 17:53
  • 4
    @Paul Ruane: I agree that it could be useful in certain cases, however, the CLR is language-agnostic, and there'd be no way to implement something like this so as to work equally well in C#, VB.NET, F#, IronPython, and all of the other CLR languages. The weird-looking name with the backtick is actually the true CLR type name; the format above is C# specific. Commented Mar 15, 2010 at 19:00
  • 11
    There could be at least an helper function in the Microsoft.CSharp namespace. That namespace contains language-specific classes. Commented Jun 21, 2010 at 15:42
  • While this answer is really great, it will not work when dealing with nested classes, or with arrays of generic objects, see my answer for a full solution Commented Apr 4, 2014 at 5:10
6

While the accepted solution is good for just the name or for a non nested full name (by replacing name to full name as in @Ose E's answer), however for nested types it will still not work, and also not for arrays of generic types.

So here is a solution that will work:

public static string ToGenericTypeString(this Type t, params Type[] arg)
{
 if (t.IsGenericParameter || (t.FullName is null && !t.IsNested)) return t.Name;//Generic argument stub
 else if (t.FullName is null && t.IsNested) return ToGenericTypeString(t.DeclaringType) + "." + t.Name;
 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.Name.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.Name;
 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.Name.Replace(genericType.Name, "").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;
}
answered Apr 2, 2014 at 23:07
0
4

This will result in exactly the same code result like the cs code generator. I've improved the code of 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;
 }

this will pass the following unit test as expected.

[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>
 {
 }
 }
}

Result will be:

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>>>
answered Aug 16, 2017 at 16:31
3
  • Very good. Thank you. Commented May 24 at 0:24
  • FYI it fails for int[,,], int[,,,], int[,,,,]... but I am not complaining. Commented May 24 at 0:26
  • FYI it also correctly handles the following, which you did not test for: typeof( List<> ), typeof( List<>.Enumerator ), typeof( Dictionary<,> ), typeof( Dictionary<,>.Enumerator ), etc. Commented May 24 at 0:40
0

This is my solution, it's also working for nested classes and generics:

 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;
 }
answered Feb 25, 2016 at 10:20
-1

Minor addition to @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 + ">";
}
answered Feb 12, 2014 at 2:34
2
  • 1
    It looks like the only thing you've changed is from Name to FullName? I'm not sure if that's materially changing enough to justify an alternative answer - it should probably have been posted as a comment. Commented Feb 26, 2014 at 16:32
  • It is not an alternative. Fullname helps with namespace resolution. Commented Feb 26, 2014 at 19:18

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.