See, .NET is not limited to just C #. There are aliases in C #, which may not be in other .NET languages. Then, the recording format of generic types in (for example) VB is completely different. Therefore, the framework can not give you a name as it is written in C #, because it does not even know what language module is currently being executed!
Therefore, without a "manual" conversion is not enough.
Slightly expanded the solution mentioned in the comments : added Nullable , tuples (stupid), as well as “open” generic types. Limitations of this method: the names of the elements of the tuples, as well as dynamic , are not detected, since the information about this is encoded not in the type, but in the attribute hung on the field.
public static class TypeExtensions { private static readonly Dictionary<Type, string> typeToAlias = new Dictionary<Type, string> { { typeof(string), "string" }, { typeof(object), "object" }, { typeof(bool), "bool" }, { typeof(byte), "byte" }, { typeof(char), "char" }, { typeof(decimal), "decimal" }, { typeof(double), "double" }, { typeof(short), "short" }, { typeof(int), "int" }, { typeof(long), "long" }, { typeof(sbyte), "sbyte" }, { typeof(float), "float" }, { typeof(ushort), "ushort" }, { typeof(uint), "uint" }, { typeof(ulong), "ulong" }, { typeof(void), "void" } }; public static string GetFriendlyName(this Type type) { if (typeToAlias.TryGetValue(type, out var reservedName)) return reservedName; if (type.IsArray) return type.GetElementType().GetFriendlyName() + "[]"; var nullableBase = Nullable.GetUnderlyingType(type); if (nullableBase != null) return nullableBase.GetFriendlyName() + "?"; var tupleTypes = GetTupleTypes(type); if (tupleTypes != null) { var tupleTypeNames = tupleTypes.Select(t => t.GetFriendlyName()); return "(" + string.Join(", ", tupleTypeNames) + ")"; } var friendlyName = type.Name; if (type.IsGenericType) { int backtick = friendlyName.IndexOf('`'); if (backtick > 0) friendlyName = friendlyName.Remove(backtick); var genericArgs = type.GetGenericArguments(); if (type.IsGenericTypeDefinition) friendlyName += "<" + new string(',', genericArgs.Length - 1) + ">"; else friendlyName += "<" + string.Join(", ", genericArgs.Select(t => t.GetFriendlyName())) + ">"; } return friendlyName; } static readonly HashSet<Type> tupleTypes = new HashSet<Type>() { typeof(ValueTuple<>), typeof(ValueTuple<,>), typeof(ValueTuple<,,>), typeof(ValueTuple<,,,>), typeof(ValueTuple<,,,,>), typeof(ValueTuple<,,,,,>), typeof(ValueTuple<,,,,,,>) }; static IEnumerable<Type> GetTupleTypes(Type type) { if (type == typeof(ValueTuple)) return Enumerable.Empty<Type>(); if (!type.IsGenericType) return null; var def = type.GetGenericTypeDefinition(); if (tupleTypes.Contains(def)) return type.GetGenericArguments(); if (def == typeof(ValueTuple<,,,,,,,>)) { var args = type.GetGenericArguments(); var lastArg = args[args.Length - 1]; var restTypes = GetTupleTypes(lastArg); if (restTypes != null) return args.Take(args.Length - 1).Concat(restTypes); } return null; } }