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>
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
- Nested Types
- Illegal C# Names
- 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();
}
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);
}
}
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);
}
}
string text = dictionary.ToString();
provides almost what you are asking for:
System.Collections.Generic.Dictionary`2[System.String,System.DateTime]
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.
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.