2025-05-25

Human-readable names of dotnet types in C# notation

Summary

Type names as reported by the dotnet runtime are in a cryptic, non-human-readable format. Attempts by many to solve this problem have generally been naive, incomplete, and clunky. A library that gets the job done right is presented.

The problem

PEARL: In dotnet, the `System.Type.Name` and `System.Type.FullName` properties return type names in a cryptic format which is not human-readable and bears very little resemblance to the names of the same types as they appear in C# source code.

Try this:

Console.WriteLine( typeof( int ).Name );

Instead of int, you get Int32.

Try this:

class Outer{ public class Inner; }
Console.WriteLine( typeof( Outer.Inner ).FullName );

Instead of Outer.Inner you get Outer+Inner.

Try this:

Console.WriteLine( typeof( Dictionary<,> ).Name );

Instead of Dictionary<,> you get Dictionary`2.

Try this:

Console.WriteLine( new Dictionary<int, System.DateTime>()
        .GetType().FullName );

Instead of System.Collections.Generic.Dictionary<int, System.DateTime> you get the following:

System.Collections.Generic.Dictionary`2[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.DateTime, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

Try this:

class C { public int? Field; }
Console.WriteLine( typeof( C ).GetField( "Field" )!.FieldType.FullName );

Instead of int? you get something like the following:

System.Nullable`1[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

The examples above demonstrate the following problems:

  • The language keywords for built-in types are not used; instead, the raw CLR type names are used.
  • Names of nested classes are delimited with a plus-sign instead of a period.
  • Names of generic types do not use angle-bracket notation; instead, they are suffixed with a back-quote character, followed by the number of generic parameters they accept. The original generic type parameter names are nowhere to be found.
  • Names of constructed generic types are further suffixed with a list of fully qualified (assembly-qualified) type names, one for each generic type argument.
  • Names of nullable value types do not use the question-mark shorthand notation; instead, the System.Nullable<T> type is fully spelled out.

Obviously, these cryptic names are intended to be parsed by software, not by humans. Additionally, there are many different programming languages in the dotnet ecosystem, each with its own syntax for types, so the chosen notation does not favor any particular language.

That's all very fine, and it should not really be a problem, because among the tens of thousands of APIs built into dotnet, there must surely be one for obtaining the human-readable name of a type in C# notation, right?

right?

Well, unfortunately, no.

There is no such thing built into dotnet.

The closest there is to it involves referencing the System.CodeDom assembly (by Microsoft) and performing the following magical incantations:

var cSharpCompiler = new Microsoft.CSharp.CSharpCodeProvider();
var typeRef = new System.CodeDom.CodeTypeReference( type );
string typeName = cSharpCompiler.GetTypeOutput( typeRef );

This will give you namespace-qualified human-readable type names in C# notation, and it will even replace names of built-in types with their corresponding language keywords, so it will give you int instead of System.Int32, which is nice.

However, even then, the generated type names suffer in the following ways:

  1. If you wanted the underlying CLR type names instead of the language keywords, you can't have them. 
  2. If you wanted type names without namespaces, you can't have them.
  3. The question-mark shorthand notation for nullable value types is not used, so you get System.Nullable<int> instead of int?
  4. Tuple notation is not used, so you get ValueTuple<int,char> instead of (int,char).
  5. Generic type arguments are stripped from generic type definitions, so you get System.Collections.Generic.Dictionary<,>. If you wanted the full original generic type definition, which is  System.Collections.Generic.Dictionary<TKey,TValue>, you can't have it.
  6. Although language keywords are used for most built-in types, they are not used for nint and nuint, which appear as System.IntPtr and System.UIntPtr respectively. 

None of the above behavior can be changed, because the mechanism is not customizable.

Prior attempts to solve the problem

Many have asked for a function that, given a type, returns its human-readable name in C# notation, and many have tried to offer such a function.

On Stack Overflow there is not just one, but several questions asking this, or variations of it, and each question has received several (attempted) answers:

On GitHub there is an open-source project which aims to do this:

GitHub: BanallyMe / ReadableTypeNames

Someone has even created a video on YouTube aiming to explain how to do this:

YouTube: vlogize: Generating a Human Readable Type Name in C#: Solving the ICollection Challenge

Needless to say, all of the above attempts are incomplete and clunky. Some people provide functions that only work for a specific set of types, such as List<> and Dictionary<,> but fail for everything else; others provide functions that try to work for any type, but fail in all sorts of edge cases, and even not-so-edge cases, for example with arrays or with nested types. Even the best solutions fail to cover all cases. Furthermore, virtually all of the code in these solutions is of very poor quality, engaging in excessive string searching, string substitution, string concatenation, etc.  It is really so messy that it cannot be improved; it has to be thrown away and re-written from scratch.

The solution

I hereby present to the world this open-source project I created:

GitHub: MikeNakis / MikeNakis.CSharpTypeNames

It is a tiny library that generates human-readable dotnet type names in C# notation, and it does it right.

More information in the README file on GitHub.

No comments:

Post a Comment