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));
}
History
- July 16th, 2013 -- First posting.
- January 26th, 2015 -- Fixed a bug and updated text of the article.
Software developer with a bachelor degree and a few years experience (studied computer science at a university as well, but didn't finish). Really love C#, but work usually involves Java. Some experience with VB and C as well.
When hobbying I almost exclusively use C# and AutoHotkey. In a previous life I did some Haskell and Modula-3 and similar stuff. I enjoy games and solving puzzles like on http://projecteuler.net/ (I think I'm stuck at level 4, though)