26

I am working on code generation and ran into a snag with generics. Here is a "simplified" version of what is causing me issues.

Dictionary<string, DateTime> dictionary = new Dictionary<string, DateTime>();
string text = dictionary.GetType().FullName;

With the above code snippet the value of text is as follows:

 System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, 
 Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.DateTime, mscorlib, 
 Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

(Line breaks added for better readability.)

Is there a way to get the type name (type) in a different format without parsing the above string? I desire the following result for text:

System.Collections.Generic.Dictionary<System.String, System.DateTime>
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
Jamey McElveen
  • 18,135
  • 25
  • 89
  • 129
  • 2
    Note that if you remove `.FullName` and use `.ToString()` instead, you get the "text" ``System.Collections.Generic.Dictionary`2[System.String,System.DateTime]`` which is more readable, and close to what you want. – Jeppe Stig Nielsen Oct 01 '13 at 12:39

6 Answers6

32

There is no built-in way to get this representation in the .Net Framework. Namely because there is no way to get it correct. There are a good number of constructs that are not representable in C# style syntax. For instance "<>foo" is a valid type name in IL but cannot be represented in C#.

However, if you're looking for a pretty good solution it can be hand implemented fairly quickly. The below solution will work for most situations. It will not handle

  1. Nested Types
  2. Illegal C# Names
  3. Couple of other scenarios

Example:

public static string GetFriendlyTypeName(Type type) {
    if (type.IsGenericParameter)
    {
        return type.Name;
    }

    if (!type.IsGenericType)
    {
        return type.FullName;
    }

    var builder = new System.Text.StringBuilder();
    var name = type.Name;
    var index = name.IndexOf("`");
    builder.AppendFormat("{0}.{1}", type.Namespace, name.Substring(0, index));
    builder.Append('<');
    var first = true;
    foreach (var arg in type.GetGenericArguments())
    {
        if (!first)
        {
            builder.Append(',');
        }
        builder.Append(GetFriendlyTypeName(arg));
        first = false;
    }
    builder.Append('>');
    return builder.ToString();
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • if you get a chance edit your answer and include "static string GetFriendlyTypeName(Type type) { if (type.IsGenericParameter) { return type.Name; }" in the code block :) – Jamey McElveen Dec 31 '08 at 14:39
  • @Jamey, done. This is actually pretty weird. The code block would not add the first line until I added a non-code line between the ordered list and the start of the block. – JaredPar Dec 31 '08 at 14:49
  • and add on top: bool bArray = false; if (type.IsArray) { bArray = true; type = type.GetElementType(); } and on bottom if (bArray) builder.Append("[]"); return builder.ToString(); to support arrays. – Wolf5 Mar 23 '09 at 13:05
  • You might want to handle Nullables, and you might want to add a space after the comma. – SLaks Jul 05 '11 at 14:38
  • the first block " if (type.IsGenericParameter) {...} " may not be a good idea, since the generic type parameter may be a generic type itself. – Johnny5 Jul 05 '11 at 14:58
  • Stuff like `GetFriendlyTypeName(new List().GetEnumerator().GetType())` or `GetFriendlyTypeName(typeof(List<>.Enumerator))` leads to an exception. These are types that are nested inside generic outer types. – Jeppe Stig Nielsen Oct 01 '13 at 12:31
23

A good and clean alternative, thanks to @LukeH's comment, is

using System;
using System.CodeDom;
using System.Collections.Generic;
using Microsoft.CSharp;
//...
private string GetFriendlyTypeName(Type type)
{
    using (var p = new CSharpCodeProvider())
    {
        var r = new CodeTypeReference(type);
        return p.GetTypeOutput(r);
    }
}
Community
  • 1
  • 1
bluish
  • 26,356
  • 27
  • 122
  • 180
  • 4
    much better then marked as answer(subclasses of generic classes are shown correctly) – FLCL Feb 03 '14 at 19:54
4

This evening I was toying a bit with extension methods and I tried to find an answer for your question. Here is the result: it's a no-warranty code. ;-)

internal static class TypeHelper
{
    private const char genericSpecialChar = '`';
    private const string genericSeparator = ", ";

    public static string GetCleanName(this Type t)
    {
        string name = t.Name;
        if (t.IsGenericType)
        {
            name = name.Remove(name.IndexOf(genericSpecialChar));
        }
        return name;
    }

    public static string GetCodeDefinition(this Type t)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0}.{1}", t.Namespace, t.GetCleanName());
        if (t.IsGenericType)
        {
            var names = from ga in t.GetGenericArguments()
                        select GetCodeDefinition(ga);
            sb.Append("<");
            sb.Append(string.Join(genericSeparator, names.ToArray()));
            sb.Append(">");
        }
        return sb.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        object[] testCases = { 
                                new Dictionary<string, DateTime>(),
                                new List<int>(),
                                new List<List<int>>(),
                                0
                            };
        Type t = testCases[0].GetType();
        string text = t.GetCodeDefinition();
        Console.WriteLine(text);
    }
}
Fabrizio C.
  • 1,544
  • 10
  • 12
2
string text = dictionary.ToString();

provides almost what you are asking for:

System.Collections.Generic.Dictionary`2[System.String,System.DateTime]
Walt Gaber
  • 31
  • 3
0

I don't think .NET has anything built-in that would do this, so you will have to do it yourself. I think that the reflection classes provide quite enough information to reconstruct the type name in this form.

Vilx-
  • 104,512
  • 87
  • 279
  • 422
  • I have tried this path can you elaborate on the solution? I have found not properties or collections that give me this information I can use to reconstruct the declaration. – Jamey McElveen Dec 30 '08 at 22:38
0

I believe you can pass

System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

into Type.Parse(). That is a fully qualified type name, I think.

bluish
  • 26,356
  • 27
  • 122
  • 180
jcollum
  • 43,623
  • 55
  • 191
  • 321
  • Not needed. The original Dictionary.GetType() already contains a collection of type parameters which you can access directly. No need for parsing to get the same result. – Vilx- Dec 30 '08 at 22:28