56

I need some way to get the Name of a Type, when type.IsGenericType = true.

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

What I want, is a message box to pop up with List showing... how can I do that?

11 Answers 11

78

You can implement an extension method to get the "friendly name" of a type, like this:

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

With this in your project, you can now say:

MessageBox.Show(t.GetFriendlyName()); 

And it will display "List<String>".

I know the OP didn't ask for the generic type parameters, but I prefer it that way. ;-)

Namespaces, standard aliases for built-in types, and use of StringBuilder left as an exercise for the reader. ;-)

Sign up to request clarification or add additional context in comments.

4 Comments

With new C# loveliness, you can now write all the bit after the backtick check in one (albeit longish) line, and this will also deal with nested generics: friendlyName += $"<{string.Join(",", type.GetGenericArguments().Select(p => type.GetFriendlyName()))}>"
You might want to use recursion for nested generic types: string typeParamName = typeParameters[i].GetFriendlyName();
Note that this does not work for arrays of generic types, they have names such as MyType`1[] and thus the trailing array brackets [] will be removed by friendlyName.Remove(iBacktick);. You need to use type.IsArray to test for this and type.GetArrayRank() to determine how many dimensions the array had.
@joshcomley you're going to make the stack unhappy if you use type inside the Select instead of p. ;)
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" } 

2 Comments

In case you need the type T of a generic type like List<T> you can use something like this t.GetGenericArguments()[0].Name. I needed this a while ago and couldn't find it anywhere. This will return string in the case you have a List<string>
If you remove everything after the backtick you loose any array brackets that might be present.
27

My take on yoyo's approach. Ensures more friendly names for primitives, handles arrays and is recursive to handle nested generics. Also unit tests.

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

4 Comments

Does this compile? I had to tweak a bit. Edited for corrections.
You forgot about Nullable. To prettify nullables you should use something like this: if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) return type.GetGenericArguments().First().GetFriendlyName() + "?";
This has the same problem as yoyo's answer in that it doesn't check for arrays and strips away any [] brackets from the name. GetFriendlyName(typeof(Foo<Bar<Bas>[]>)) returns Foo<Bar<Bas>>.
The aliasing doesn't handle array's either; Use type.IsArray and type.GetElementType() to resolve aliases for arrays. I updated my answer about type aliases to show how one can do that.
10

Assuming you just want to see that its List<T> instead of List<string> you'd need to do:

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

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

Comments

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

Comments

7

Here is my take on this. I did not put the backtick check since for what I see, it's always there. You can add it if you want but I like to keep things simple.

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

Comments

6

I know this is an old question, but a colleague and myself needed to do this for some intellisense/roslyn work. The optimal solution appeared to be Ali's solution, but it doesn't work for nested types:

 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 

In order to solve these issues, I converted the function into a recursive method:

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

running for the previous examples/test cases yielded the following outputs:

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

I spent some time trying to resolve the nested types issue so wanted to document this here to ensure anyone else in future can save some considerable time (and headaches!). I have checked the performance as well, and it is in the microseconds to complete (8 microseconds in the case of the last scenario:

Performance results
(Variables names used from original scenario list)
"i" | 43uS
"listTest" | 3uS
"dictTest" | 2uS
"nestTest" | 5uS
"superNestTest" | 9uS
"superNestTest2" | 9uS
Average times after performing the above code 200 times on each scenario

1 Comment

This is the best IMO, should be a standard method in .NET Standard/Core. I changed out the type.FullName and type.AssemblyQualifiedName and type.ToString() for a simple type.Name though because I wanted the shorter names for a code completion engine I'm writing where Roslyn isn't available.
6

Here is a complete implementation based on the previous answers supporting both Aliases (including Nullable) and Arrays:

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

Tested with nonsense such as:

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

And it does indeed return: "Dictionary<string[,], List<int?[,][]>[,,]>[]"


Edit: Updated to properly handle enum types.

1 Comment

(For anyone curious about the 17:th "undefined" special type, it was originally reserved for one more time related type. It only existed as part of an early internal pre-1.0 C# draft, and never made it into any public release as far as I know.)
3

i have improved yoyos version for the usage in Code Generation. Note that all types are now referenced full qualified => 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('+', '.'); } 

Comments

0

In the latest version of C#, you can use:

var s = x.GetType().ShortDisplayName();

And it returns Thing<IFoo>

Edit: Sorry, that only works as an extension on EF Core. :(

Comments

0
string GetFriendlyName(Type type) => type.IsArray ? $"{GetFriendlyName(type.GetElementType())}[]" : type.IsGenericType ? $"{type.Name.Remove(type.Name.IndexOf('`'))}<{string.Join(", ", type.GetGenericArguments().Select(GetFriendlyName))}>" : type.Name; 

Works for nested generic types and arrays of Nullable<T>

//outputs Dictionary<String, List<Nullable<Int32>[]>> GetFriendlyName(typeof(Dictionary<string, List<int?[]>>)); 

Please, note that most if not all existing answers fails to display array of nullables (int?[])

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.