3
\$\begingroup\$

I'm writing a code generation library, I've exposed a couple of methods and of course in order to define a member you need to specify it's type, the easiest way is to use typeof(Type), which I later format so that it it's compilable and I can insert it as a code chunk e.g. System.Int32, which for non-generic types is basically concatenating the namespace and the type's name.

That works fine but generic types have the backtick followed by the number of arguments, so I needed a method to format all the types, so that they are always compilable e.g. System.Collection.Generics.List<System.Int32>.

public static class TypeExtensions
{
    public static string FormatTypeName(this Type type)
    {
        if(type == null) { throw new ArgumentNullException(nameof(type)); }

        if (type.IsGenericType)
        {
            var definedArguments = type.GetGenericArguments();
            var genericIdentifier = $"`{definedArguments.Length}";

            if (definedArguments.First().FullName == null)
            {
                var genericBraces = "<".PadRight(definedArguments.Length, ',') + ">";
                return $"{type.Namespace}.{type.Name.Replace(genericIdentifier, genericBraces)}";
            }

            var formattedTypes = definedArguments.Select(definedArgument => definedArgument.FormatTypeName());

            var newGenericArguments = $"<{string.Join(", ", formattedTypes)}>";
            return $"{type.Namespace}.{type.Name.Replace(genericIdentifier, newGenericArguments, StringComparison.InvariantCultureIgnoreCase)}";
        }

        return $"{type.Namespace}.{type.Name}";
    }
}

It supports indefinite amount of nested and linear generic arguments due to the recursion that is in-place.


The way it works is pretty straight forward, there are 2 possible incomes, we have no arguments defined e.g. typeof(Dictionary<,>) or all of the arguments are defined e.g typeof(Dictionary<int, int>).

Since the C# compiler doesn't allow partially defined arguments we can simply check if the first of the arguments has a FullName (note that Name is the name pf the generic argument not the type behind it), if it does we simply concatenate all of the arguments and surround them by <>. if the first argument doesn't have value then it's empty and we just leave the braces with commas separating the arguments.


Since this will be used in a code generation tool, performance is not crucial, but always welcome as long as it doesn't damage the readability and maintainability of the code, which I consider more important in this case.

However since it will be used in a code generation tool, I'm more worried about whether I've missed some edge cases.

\$\endgroup\$
4
  • \$\begingroup\$ Since FormatTypeName is an extension method I don't think that the parameter type can ever be null. Also, if you want more performance you can take a look at this answer for how to create strings of repeated characters. stackoverflow.com/a/720915/7412948 \$\endgroup\$ – Jeffrey Parks Oct 31 '19 at 19:42
  • 3
    \$\begingroup\$ @JeffreyParks It can be null! :) \$\endgroup\$ – Denis Oct 31 '19 at 19:43
  • 1
    \$\begingroup\$ Good to know! Also, if this proves to be too slow and you have a lot of repeated type usage, you could consider caching the results into a static Dictionary<type,string> so you avoid a lot of repeated work. \$\endgroup\$ – Jeffrey Parks Oct 31 '19 at 19:45
  • 2
    \$\begingroup\$ @JeffreyParks an extension method is just syntax sugar for ExtClass.ExtMethod(obj), it's not a member call on obj ;-) \$\endgroup\$ – Mathieu Guindon Oct 31 '19 at 19:49
3
\$\begingroup\$

You can let CSharpCodeProvider format Type names:

public static string FormatTypeName(this Type type)
{
    using (var c = new CSharpCodeProvider()) {
        var r = new CodeTypeReference(type);
        return c.GetTypeOutput(r);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.