65.9K
Home

User-friendly names for Types

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (11 votes)

Jul 16, 2013

CPOL

2 min read

viewsIcon

36790

downloadIcon

240

How to display a Type name in a user-friendly format.

Introduction

The Name property of the Type class provides us with a name that most of the time does not correspond with what we type in code. For instance, a List<string> is really a List`1[String] (without the namespaces). This article provides an extension method that generates a user-friendly name for any given Type. It uses recursion to handle any name step-by-step. It will for instance take the type Tuple<string, double> and recursively find Tuple, double, and string and actually return 'Tuple<string, double>' and not 'Tuple`2[String, Double]'.

Using the code

The method, dubbed FriendlyName, allows four options to tweak the output:

  • useKeywords -- Indicates whether Types that are represented in C# by keywords should be written as such. E.g., int instead of Int32.
  • showGenericArguments -- Applies only to Generic Type Definitions (List<> and such). Indicates whether their Type parameters should be included. E.g., List<T> instead of just List<>. If true, the keywords 'in' and 'out' indicating variance will also be included. E.g., IEnumerable<out T>.
  • showDeclaringType -- Applies to nested types. Indicates whether they should be preceded by their declaring Type. E.g., MyType.NestedType instead of just NestedType.
  • compactNullable -- Indicates whether Nullable types should be written with a '?'. E.g., int? instead of Nullable<int>.

Different types of Types

Some notes on different 'types' of Types and how they are handled:

Generic Types

Generic Types have a name that ends in a back-tick and a number indicating how many Type parameters it contains. These are stripped because they are not really part of the name. (We want List, not List`1.)

Anonymous Types

Anonymous Types are implemented as generic types, so they also have a name that ends in a back-tick and a number. These are stripped, but the rest of the name is left intact. They have rather ugly names, like <>f__AnonymousType0<string, int> but that is by design, so nothing can be done to make their names more readable.

Pointers and Arrays

Pointers have a name that ends in '*'. Pointers need special handling because their element Type might be a 'special' type as well. A long* for instance is not recognized as a 'keyword type'. Depending on parameters, it must be displayed as either long* or Int64*. This is done recursively: first the long type is processed and then the '*' is added. The same goes for arrays.

Unhandled problems

The C# Type system is a complex thing. This creates some problems for FriendlyName that are not handled:

Jagged arrays

Jagged arrays are reversed. We type char[][,], but the actual type is char[,][]

Generic Types

Nested Generic Types in particular are tricky. If we take Dictionary<string, double>.Enumerator for instance, the actual type is Dictionary<TKey, TValue>.Enumerator<string, double>!

Example of use

var c = Enumerable.Repeat(0, 1).GetEnumerator();
var person = new { Name = "Someone", Age = 30 };
int? i = 1;
var tuple = Tuple.Create("", i, 
     new List<Dictionary<string, char[,]>>(), person);

Type[] types =
{
    c.GetType(),
    typeof(IEnumerable<>),
    typeof(char[][,,]),
    tuple.GetType(),
    typeof(Func<int, string, int?, List<string>>),
    typeof(Func<int, string, int?, List<string>>).GetGenericTypeDefinition(),
    typeof(Dictionary<string, double>.Enumerator),
    typeof(Nullable<>)
};

foreach (Type type in types) {
    Console.WriteLine(type.FriendlyName());
    Console.WriteLine(type.FriendlyName(useKeywords: false, showGenericArguments: false, compactNullable: false));
}

//Output:
//
//Enumerable.<RepeatIterator>d__bc<int>
//Enumerable.<RepeatIterator>d__bc<Int32>
//IEnumerable<out T>
//IEnumerable<>
//char[,,][]
//Char[,,][]
//Tuple<string, int?, List<Dictionary<string, char[,]>>, <>f__AnonymousType0<string, int>>
//Tuple<String, Nullable<Int32>, List<Dictionary<String, Char[,]>>, <>f__AnonymousType0<String, Int32>>
//Func<int, string, int?, List<string>>
//Func<Int32, String, Nullable<Int32>, List<String>>
//Func<in T1, in T2, in T3, out TResult>
//Func<,,,>
//Dictionary<TKey, TValue>.Enumerator<string, double>
//Dictionary<,>.Enumerator<String, Double>
//T?
//Nullable<>

 

History

  • July 16th, 2013 -- First posting.
  • January 26th, 2015 -- Fixed a bug and updated text of the article.