The name properties of System.Type class return a strange result in case of generic types. Is there a way to get the type name in a format closer to the way I specified it?
Example: typeof(List<string>).OriginalName == "List<string>"
The name properties of System.Type class return a strange result in case of generic types. Is there a way to get the type name in a format closer to the way I specified it?
Example: typeof(List<string>).OriginalName == "List<string>"
A minimal work solution that leverages CodeDomProvider is to control how the CodeTypeReference instance is built in the first place. There are special cases only for generic types and multi-rank arrays, so we only have to care about those:
static CodeTypeReference CreateTypeReference(Type type)
{
var typeName = (type.IsPrimitive || type == typeof(string)) ? type.FullName : type.Name;
var reference = new CodeTypeReference(typeName);
if (type.IsArray)
{
reference.ArrayElementType = CreateTypeReference(type.GetElementType());
reference.ArrayRank = type.GetArrayRank();
}
if (type.IsGenericType)
{
foreach (var argument in type.GetGenericArguments())
{
reference.TypeArguments.Add(CreateTypeReference(argument));
}
}
return reference;
}
Using this modified factory method, it is then possible to use the appropriate code provider to get pretty typing, like so:
using (var provider = new CSharpCodeProvider())
{
var reference = CreateTypeReference(typeof(IObservable<IEnumerable<Tuple<int?, string>>>[,]));
var output = provider.GetTypeOutput(reference);
Console.WriteLine(output);
}
The above code returns IObservable<IEnumerable<Tuple<Nullable<int>, string>>>[,]. The only special case that is not handled well are Nullable types but this is really more a fault of the CodeDomProvider than anything else.
I do it like this ..
public static class TypeExtensions {
public static String GetName(this Type t) {
String readable;
#if PREVENT_RECURSION
if(m_names.TryGetValue(t, out readable)) {
return readable;
}
#endif
var tArgs = t.IsGenericType ? t.GetGenericArguments() : Type.EmptyTypes;
var name = t.Name;
var format = Regex.Replace(name, @"`\d+.*", "")+(t.IsGenericType ? "<?>" : "");
var names = tArgs.Select(x => x.IsGenericParameter ? "" : GetName(x));
readable=String.Join(String.Join(",", names), format.Split('?'));
#if PREVENT_RECURSION
m_names.Add(t, readable);
#endif
return readable;
}
static readonly Dictionary<Type, String> m_names = new Dictionary<Type, String> { };
}
where PREVENT_RECURSION should be defined true if you don't want the type in generic type arguments be resolved recursively every time, just take from the cache dictionary; leave it undefined of false if you don't care that.
Combined a few answers, added support for nested types and array ranks, and turned it into an extension method with cached resolved names.
/// <summary>
/// Extension methods for <see cref="Type"/>.
/// </summary>
public static class TypeExtensions
{
/// <summary>
/// Dictionary of type names.
/// </summary>
private static readonly ConcurrentDictionary<Type, string> typeNames;
/// <summary>
/// Initializes static members of the <see cref="TypeExtensions"/> class.
/// </summary>
static TypeExtensions()
{
typeNames = new ConcurrentDictionary<Type, string>
{
[typeof(bool)] = "bool",
[typeof(byte)] = "byte",
[typeof(char)] = "char",
[typeof(decimal)] = "decimal",
[typeof(double)] = "double",
[typeof(float)] = "float",
[typeof(int)] = "int",
[typeof(long)] = "long",
[typeof(sbyte)] = "sbyte",
[typeof(short)] = "short",
[typeof(string)] = "string",
[typeof(uint)] = "uint",
[typeof(ulong)] = "ulong",
[typeof(ushort)] = "ushort"
};
}
/// <summary>
/// Gets the type name with generics and array ranks resolved.
/// </summary>
/// <param name="type">
/// The type whose name to resolve.
/// </param>
/// <returns>
/// The resolved type name.
/// </returns>
public static string ToCSTypeName(this Type type)
{
return typeNames.GetOrAdd(type, GetPrettyTypeName);
}
/// <summary>
/// Gets the type name as it would be written in C#
/// </summary>
/// <param name="type">
/// The type whose name is to be written.
/// </param>
/// <returns>
/// The type name as it is written in C#
/// </returns>
private static string GetPrettyTypeName(Type type)
{
var typeNamespace = type.DeclaringType != null ? ToCSTypeName(type.DeclaringType) : type.Namespace;
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return $"{ToCSTypeName(Nullable.GetUnderlyingType(type))}?";
}
var typeList = string.Join(", ", type.GenericTypeArguments.Select(ToCSTypeName).ToArray());
var typeName = type.Name.Split('`')[0];
return $"{typeNamespace}.{typeName}<{typeList}>";
}
if (type.IsArray)
{
var arrayRank = string.Empty.PadLeft(type.GetArrayRank() - 1, ',');
var elementType = ToCSTypeName(type.GetElementType());
return $"{elementType}[{arrayRank}]";
}
return $"{typeNamespace}.{type.Name}";
}
}
public static string pretty_name( Type type, int recursion_level = -1, bool expand_nullable = false )
{
if( type.IsArray )
{
return $"{pretty_name( type.GetElementType(), recursion_level, expand_nullable )}[]";
}
if( type.IsGenericType )
{
// find generic type name
var gen_type_name = type.GetGenericTypeDefinition().Name;
var index = gen_type_name.IndexOf( '`' );
if( index != -1 )
gen_type_name = gen_type_name.Substring( 0, index );
// retrieve generic type aguments
var arg_names = new List<string>();
var gen_type_args = type.GetGenericArguments();
foreach( var gen_type_arg in gen_type_args )
{
arg_names.Add(
recursion_level != 0
? pretty_name( gen_type_arg, recursion_level - 1, expand_nullable )
: "?" );
}
// if type is nullable and want compact notation '?'
if( !expand_nullable && Nullable.GetUnderlyingType( type ) != null )
return $"{arg_names[ 0 ]}?";
// compose common generic type format "T<T1, T2, ...>"
return $"{gen_type_name}<{string.Join( ", ", arg_names )}>";
}
return type.Name;
}
My two cents: