C#: "Pretty" type name function?
Asked Answered
C

13

24

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>"

Carcajou answered 19/6, 2011 at 14:12 Comment(2)
This is pretty easy to write yourself as an extension method using recursion.Duane
Such function is not in the framework, because it depends on the language you use: VB has different representation of generics and I'm sure other languages too.Sheldonshelduck
C
0

I understood, that I have to write this myself. Here is my solution (it is actually a bit more than asked for). It is, probably, helpfull.

using System.Reflection;
using HWClassLibrary.Debug;
using System.Collections.Generic;
using System.Linq;
using System;

namespace HWClassLibrary.Helper
{
    public static class TypeNameExtender
    {
        private static IEnumerable<Type> _referencedTypesCache;

        public static void OnModuleLoaded() { _referencedTypesCache = null; }

        public static string PrettyName(this Type type)
        {
            if(type == typeof(int))
                return "int";
            if(type == typeof(string))
                return "string";

            var result = PrettyTypeName(type);
            if(type.IsGenericType)
                result = result + PrettyNameForGeneric(type.GetGenericArguments());
            return result;
        }

        private static string PrettyTypeName(Type type)
        {
            var result = type.Name;
            if(type.IsGenericType)
                result = result.Remove(result.IndexOf('`'));

            if (type.IsNested && !type.IsGenericParameter)
                return type.DeclaringType.PrettyName() + "." + result;

            if(type.Namespace == null)
                return result;

            var conflictingTypes = ReferencedTypes
                .Where(definedType => definedType.Name == type.Name && definedType.Namespace != type.Namespace)
                .ToArray();

            var namespaceParts = type.Namespace.Split('.').Reverse().ToArray();
            var namespacePart = "";
            for(var i = 0; i < namespaceParts.Length && conflictingTypes.Length > 0; i++)
            {
                namespacePart = namespaceParts[i] + "." + namespacePart;
                conflictingTypes = conflictingTypes
                    .Where(conflictingType => (conflictingType.Namespace + ".").EndsWith(namespacePart))
                    .ToArray();
            }

            return namespacePart + result;
        }

        private static IEnumerable<Type> ReferencedTypes
        {
            get
            {
                if(_referencedTypesCache == null)
                    _referencedTypesCache = Assembly.GetEntryAssembly().GetReferencedTypes();
                return _referencedTypesCache;
            }
        }

        private static string PrettyNameForGeneric(Type[] types)
        {
            var result = "";
            var delim = "<";
            foreach(var t in types)
            {
                result += delim;
                delim = ",";
                result += t.PrettyName();
            }
            return result + ">";
        }
    }
}
Carcajou answered 28/7, 2011 at 20:32 Comment(0)
A
31

The problem with "pretty" names is they are different depending on the language you are using. Imagine the surprise of a VB.NET developer if OriginalName returned C# syntax.

However, it's pretty fairly easy to make this yourself:

private static string PrettyName(Type type)
{
    if (type.GetGenericArguments().Length == 0)
    {
        return type.Name;
    }
    var genericArguments = type.GetGenericArguments();
    var typeDefinition = type.Name;
    var unmangledName = typeDefinition.Substring(0, typeDefinition.IndexOf("`"));
    return unmangledName + "<" + String.Join(",", genericArguments.Select(PrettyName)) + ">";
}

This will recursively resolve the unmanaged name, so that if you have something like Dictionary<string, IList<string>> it should still work.

Anatolic answered 19/6, 2011 at 14:24 Comment(1)
Ideally this needs further development to handle types like Dictionary<string,IList<string>>.KeyCollection. Its CLR type name is Dictionary`2+KeyCollection[System.String,IList`1[System.String]] (give or take namespace qualifiers). Furthermore you can have things like Outer`1+Inner`1[OuterArg,InnerArg]. And of course you can have arrays (1D 2D etc.) and pointers to a generic type, which need handling. :)Selfdeceit
K
21

I used CodeDomProvider to convert to c#:

    public static string GetOriginalName(this Type type)
    {
        string TypeName = type.FullName.Replace(type.Namespace + ".", "");//Removing the namespace

        var provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp"); //You can also use "VisualBasic"
        var reference = new System.CodeDom.CodeTypeReference(TypeName);

        return provider.GetTypeOutput(reference);
    }
Kieffer answered 19/6, 2011 at 14:27 Comment(2)
What if I wanted to leave out namespaces?Sheldonshelduck
You could use type.Name instead of the type.FullName I guess so you don't have to remove the namespace yourself. Sorry, I see now you don't get the generic parameters that way.Ironlike
S
3

Like Harold Hoyer's answer but including nullables and a few more built-in types:

/// <summary>
/// Get full type name with full namespace names
/// </summary>
/// <param name="type">
/// The type to get the C# name for (may be a generic type or a nullable type).
/// </param>
/// <returns>
/// Full type name, fully qualified namespaces
/// </returns>
public static string CSharpName(this Type type)
{
    Type nullableType = Nullable.GetUnderlyingType(type);
    string nullableText;
    if (nullableType != null)
    {
        type = nullableType;
        nullableText = "?";
    }
    else
    {
        nullableText = string.Empty;
    }

    if (type.IsGenericType)
    {
        return string.Format(
            "{0}<{1}>{2}", 
            type.Name.Substring(0, type.Name.IndexOf('`')), 
            string.Join(", ", type.GetGenericArguments().Select(ga => ga.CSharpName())), 
            nullableText);
    }

    switch (type.Name)
    {
        case "String":
            return "string";
        case "Int32":
            return "int" + nullableText;
        case "Decimal":
            return "decimal" + nullableText;
        case "Object":
            return "object" + nullableText;
        case "Void":
            return "void" + nullableText;
        default:
            return (string.IsNullOrWhiteSpace(type.FullName) ? type.Name : type.FullName) + nullableText;
    }
}
Scorpaenid answered 21/1, 2014 at 21:29 Comment(0)
P
3

Here is my implementation. It was created to describe methods, so it handles the ref and out keywords.

private static Dictionary<Type, string> shorthandMap = new Dictionary<Type, string>
{
    { typeof(Boolean), "bool" },
    { typeof(Byte), "byte" },
    { typeof(Char), "char" },
    { typeof(Decimal), "decimal" },
    { typeof(Double), "double" },
    { typeof(Single), "float" },
    { typeof(Int32), "int" },
    { typeof(Int64), "long" },
    { typeof(SByte), "sbyte" },
    { typeof(Int16), "short" },
    { typeof(String), "string" },
    { typeof(UInt32), "uint" },
    { typeof(UInt64), "ulong" },
    { typeof(UInt16), "ushort" },
};

private static string CSharpTypeName(Type type, bool isOut = false)
{
    if (type.IsByRef)
    {
        return String.Format("{0} {1}", isOut ? "out" : "ref", CSharpTypeName(type.GetElementType()));
    }
    if (type.IsGenericType)
    {
        if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            return String.Format("{0}?", CSharpTypeName(Nullable.GetUnderlyingType(type)));
        }
        else
        {
            return String.Format("{0}<{1}>", type.Name.Split('`')[0],
                String.Join(", ", type.GenericTypeArguments.Select(a => CSharpTypeName(a)).ToArray()));
        }
    }
    if (type.IsArray)
    {
        return String.Format("{0}[]", CSharpTypeName(type.GetElementType()));
    }

    return shorthandMap.ContainsKey(type) ? shorthandMap[type] : type.Name;
}

The calling code looks like this:

string line = String.Format("{0}.{1}({2})",
    method.DeclaringType.Name,
    method.Name,
    String.Join(", ", method.GetParameters().Select(p => CSharpTypeName(p.ParameterType, p.IsOut) + " " + p.Name).ToArray()));

Where method is a MethodInfo instance.

One note: I didn't have a need to describe any multi-dimensional array types, so I didn't bother implementing description for that, but it would be fairly easy to add by calling type.GetArrayRank().

Prognathous answered 7/8, 2015 at 14:6 Comment(0)
S
3

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.

Segura answered 11/1, 2018 at 3:21 Comment(0)
B
2

You have to write this yourself. Keep in mind that Type.Name etc. are invoking methods that live in the CLR and can be invoked from multiple languages. This is why they don't come back looking like C# or VB or the language the caller was coded in, but instead looking like the CLR representation.

Note further that string and what not are aliases for CLR types like System.String. Again, this plays a role in the formatting that you see.

It's not hard to do using reflection, but I question the value of it.

Bermudez answered 19/6, 2011 at 14:23 Comment(0)
S
1

First: Kudos to Navid for wheel reinvention avoidance. I would upvote if I could.

Here are a few points to add, if anyone goes down this path (at least for VS10/.Net 4):
* Try using one of the variants of CodeTypeReference with Type arguments. This avoids a number of pitfalls of using string type names (such as trailing &) and means you get back bool instead of System.Boolean etc. You do get back the full namespace for a lot of types like this but you can always get rid of the namespace part later.
* Simple Nullables tend to come back in the form System.Nullable<int> rather than int? - You can convert to the nicer syntax with a regex on the answer such as: const string NullablePattern = @"System.Nullable<(?<nulledType>[\w\.]+)>"; const string NullableReplacement = @"${nulledType}?"; answer = Regex.Replace(answer, NullablePattern, NullableReplacement);
* The Type of a method argument like out T? gives a very opaque string. If anyone has an elegant way of dealing with things like this I would love to know about it.

Hopefully this all becomes very easy with Roslyn.

Sjoberg answered 9/2, 2015 at 17:57 Comment(1)
Sorry, Nullable pattern was scrambled. Should be <code>@"System\.Nullable<(?<nulledType>[\w\.]+)>";</code>Sjoberg
G
0

as in your example you can expect the type so you can try that

public class test<T> where T : class
{
    public List<String> tt
    {
        get;
        set;
    }
}
 ///////////////////////////
 test<List<String>> tt = new  test<List<String>>();
if(tt.GetType().FullName.Contains(TypeOf(List<String>).FullName))
{
   //do something
}
else
{
    //do something else
}
Goda answered 19/6, 2011 at 14:24 Comment(0)
O
0

I understand that you want to compare types.
The best way to do this is...
myVar is List<string> or
myVar.GetType() == myOtherVar.GetType()

If you don't need this... please disregard my answer.

Ott answered 19/6, 2011 at 14:58 Comment(0)
C
0

I understood, that I have to write this myself. Here is my solution (it is actually a bit more than asked for). It is, probably, helpfull.

using System.Reflection;
using HWClassLibrary.Debug;
using System.Collections.Generic;
using System.Linq;
using System;

namespace HWClassLibrary.Helper
{
    public static class TypeNameExtender
    {
        private static IEnumerable<Type> _referencedTypesCache;

        public static void OnModuleLoaded() { _referencedTypesCache = null; }

        public static string PrettyName(this Type type)
        {
            if(type == typeof(int))
                return "int";
            if(type == typeof(string))
                return "string";

            var result = PrettyTypeName(type);
            if(type.IsGenericType)
                result = result + PrettyNameForGeneric(type.GetGenericArguments());
            return result;
        }

        private static string PrettyTypeName(Type type)
        {
            var result = type.Name;
            if(type.IsGenericType)
                result = result.Remove(result.IndexOf('`'));

            if (type.IsNested && !type.IsGenericParameter)
                return type.DeclaringType.PrettyName() + "." + result;

            if(type.Namespace == null)
                return result;

            var conflictingTypes = ReferencedTypes
                .Where(definedType => definedType.Name == type.Name && definedType.Namespace != type.Namespace)
                .ToArray();

            var namespaceParts = type.Namespace.Split('.').Reverse().ToArray();
            var namespacePart = "";
            for(var i = 0; i < namespaceParts.Length && conflictingTypes.Length > 0; i++)
            {
                namespacePart = namespaceParts[i] + "." + namespacePart;
                conflictingTypes = conflictingTypes
                    .Where(conflictingType => (conflictingType.Namespace + ".").EndsWith(namespacePart))
                    .ToArray();
            }

            return namespacePart + result;
        }

        private static IEnumerable<Type> ReferencedTypes
        {
            get
            {
                if(_referencedTypesCache == null)
                    _referencedTypesCache = Assembly.GetEntryAssembly().GetReferencedTypes();
                return _referencedTypesCache;
            }
        }

        private static string PrettyNameForGeneric(Type[] types)
        {
            var result = "";
            var delim = "<";
            foreach(var t in types)
            {
                result += delim;
                delim = ",";
                result += t.PrettyName();
            }
            return result + ">";
        }
    }
}
Carcajou answered 28/7, 2011 at 20:32 Comment(0)
P
0

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.

Prognostic answered 23/5, 2019 at 19:3 Comment(0)
R
0

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}";
    }
}
Rhapsodize answered 15/11, 2020 at 8:46 Comment(0)
A
0
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:

  • Everything is done through the Type interface
  • No Regex
  • No extra objects created except for the list that holds generic argument names
  • Supports infinite recursion or recursion to a certain level (or no recursion at all!)
  • Supports nullable types (both formats "Nullable<>" and "?")
  • Supports ranked arrays
Assistance answered 7/10, 2021 at 14:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.