Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Given a type instance, how to get generic type name in C#?

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>".

like image 317
George Polevoy Avatar asked Mar 15 '10 16:03

George Polevoy


2 Answers

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>>> 
like image 147
Aaronaught Avatar answered Oct 05 '22 23:10

Aaronaught


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, (but note that this solution will only set the actual arguments, only if all arguments are set, and in other words even if the declaring type has supplied type arguemts, as long as the innermost generic type has not, it will still not show up even for the base).

    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;     } 
like image 27
yoel halb Avatar answered Oct 05 '22 22:10

yoel halb