19

Is there an easy way without writing a recursive method which will give a 'user friendly' name for a generic type from the Type class?

E.g. For the following code I want something like 'List<Dictionary<Int>>' instead of the shorthand or full name given by the following code:

var list = new List<Dictionary<int, string>>();
var type = list.GetType();

Console.WriteLine(type.Name);
Console.WriteLine(type.FullName);
AwkwardCoder
  • 24,893
  • 27
  • 82
  • 152
  • In short, no. Recursive method is the way to go :) – Russ Cam May 09 '13 at 16:22
  • Added the correct html formatting for greater & less than... – AwkwardCoder May 09 '13 at 16:42
  • I've written a `FriendlyName` extension method to `System.Type` in [this NuGet package](https://www.nuget.org/packages/ZSpitz.Util/) which does this. Add a reference to the package, add an appropriate `using` (`using ZSpitz.Util;`) and call `var s = type.FriendlyName(Language.CSharp);`. – Zev Spitz Aug 13 '20 at 17:11

6 Answers6

40

Based on your edited question, you want something like this:

public static string GetFriendlyName(this Type type)
{
    if (type == typeof(int))
        return "int";
    else if (type == typeof(short))
        return "short";
    else if (type == typeof(byte))
        return "byte";
    else if (type == typeof(bool)) 
        return "bool";
    else if (type == typeof(long))
        return "long";
    else if (type == typeof(float))
        return "float";
    else if (type == typeof(double))
        return "double";
    else if (type == typeof(decimal))
        return "decimal";
    else if (type == typeof(string))
        return "string";
    else if (type.IsGenericType)
        return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(x => GetFriendlyName(x)).ToArray()) + ">";
    else
        return type.Name;
}
David Gardiner
  • 16,892
  • 20
  • 80
  • 117
Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
  • 2
    I was going recommend using `FullName` and Substring...You could use split as well, on the `.`, the just taking whatever is left after the last `.` – Nevyn May 09 '13 at 16:21
  • The question specifically mentions "without writing a recursive method", though I cannot see why. –  May 09 '13 at 16:28
  • 8
    @hvd, yeah, I don't really care about arbitrary and spurious requirements by an OP. – Kirk Woll May 09 '13 at 16:34
15

You can avoid writing a recursive method by calling the recursive method that's already provided for you:

static string GetTypeName(Type type)
{
    var codeDomProvider = CodeDomProvider.CreateProvider("C#");
    var typeReferenceExpression = new CodeTypeReferenceExpression(new CodeTypeReference(type));
    using (var writer = new StringWriter())
    {
        codeDomProvider.GenerateCodeFromExpression(typeReferenceExpression, writer, new CodeGeneratorOptions());
        return writer.GetStringBuilder().ToString();
    }
}

Note that this includes the type namespaces, but excludes the assembly references. For the type in your question, the result looks like this:

System.Collections.Generic.List<System.Collections.Generic.Dictionary<int, string>>

It isn't clear to me whether that qualifies as "something like" List<Dictionary<int, string>>.

  • I just do a post cleanup where I loop through all my known namespaces after the fact.: string res = GetTypeName(myType).Replace(namepace + ".", ""); – Jroonk Jul 14 '20 at 23:49
6

I used this code, when I needed a solution:

    public static string FriendlyName(this Type type)
    {
        if (type.IsGenericType)
        {
            var namePrefix = type.Name.Split(new [] {'`'}, StringSplitOptions.RemoveEmptyEntries)[0];
            var genericParameters = type.GetGenericArguments().Select(FriendlyName).ToCsv();
            return namePrefix + "<" + genericParameters + ">";
        }

        return type.Name;
    }

and

    public static string ToCsv(this IEnumerable<object> collectionToConvert, string separator = ", ")
    {
        return String.Join(separator, collectionToConvert.Select(o => o.ToString()));
    }

example usage:

    var typeDisplayText = MyDataModel.GetType().FriendlyName();

...and if you're creating auto-generated developer help pages, this can also be useful, as it includes the generic parameter names:

public static string DefinitionTitle(this Type type)
    {
        if (type.IsGenericType)
        {
            var namePrefix = type.Name.Split(new[] { '`' }, StringSplitOptions.RemoveEmptyEntries)[0];
            var genericParameters = type.GetGenericArguments().Select(a => a.Name).ToCsv();
            return namePrefix + "<" + genericParameters + ">";
        }

        return type.Name;
    }

example usage:

    var typeDefinitionText = typeof(Dictionary<,>).DefinitionTitle());
Phil
  • 1,062
  • 1
  • 17
  • 15
4

Building a more complete answer off of Kirk's would look like this. Modifications:

  • Support for all C# keywords
  • Supports custom-translations
  • Arrays
  • Nullables being ValueType? instead of Nullable<ValueType>

Here's full code:

public static class TypeTranslator
{
    private static Dictionary<Type, string> _defaultDictionary = new Dictionary<System.Type, string>
    {
        {typeof(int), "int"},
        {typeof(uint), "uint"},
        {typeof(long), "long"},
        {typeof(ulong), "ulong"},
        {typeof(short), "short"},
        {typeof(ushort), "ushort"},
        {typeof(byte), "byte"},
        {typeof(sbyte), "sbyte"},
        {typeof(bool), "bool"},
        {typeof(float), "float"},
        {typeof(double), "double"},
        {typeof(decimal), "decimal"},
        {typeof(char), "char"},
        {typeof(string), "string"},
        {typeof(object), "object"},
        {typeof(void), "void"}
    };

    public static string GetFriendlyName(this Type type, Dictionary<Type, string> translations)
    {
        if(translations.ContainsKey(type))
            return translations[type];
        else if (type.IsArray)
        {
            var rank = type.GetArrayRank();
            var commas = rank > 1 
                ? new string(',', rank - 1)
                : "";
            return GetFriendlyName(type.GetElementType(), translations) + $"[{commas}]";
        }
        else if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            return type.GetGenericArguments()[0].GetFriendlyName() + "?";
        else if (type.IsGenericType)
            return type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(x => GetFriendlyName(x)).ToArray()) + ">";
        else
            return type.Name;
    }

    public static string GetFriendlyName(this Type type)
    {
        return type.GetFriendlyName(_defaultDictionary);
    }
}
Shlomo
  • 14,102
  • 3
  • 28
  • 43
0

Reflection - Getting the generic parameters from a System.Type instance

You can also use reflection on generic types:

var dict = new Dictionary<string, int>();

    Type type = dict.GetType();
    Console.WriteLine("Type arguments:");
    foreach (Type arg in type.GetGenericArguments())
    {
        Console.WriteLine("  {0}", arg);
    }

You can then put it into some extension method for object and use it anywhere you need. I would also like to add that every recursion can be written as imperative code.

So the whole code will look like:

 static void GetGenericParametersNames(Type type)
        {
            Queue<Type> typeQueue = new Queue<Type>();
            typeQueue.Enqueue(type);
            while (typeQueue.Any())
            {
                var t = typeQueue.Dequeue();
                Console.WriteLine("  {0}", arg);

                foreach (Type arg in t.GetGenericArguments())
                {
                    typeQueue.Enqueue(t);
                }
            }
        }
Community
  • 1
  • 1
VsMaX
  • 1,685
  • 2
  • 16
  • 28
-1

Use GetDisplayName()

typeof(List<Dictionary<int, string>>).GetDisplayName()

will give you

"ListOfDictionaryOfIntegerAndString"
Tomasz Sikora
  • 1,649
  • 3
  • 19
  • 30