27

I'm using reflection to print out a method signature, e.g.

foreach (var pi in mi.GetParameters()) {
    Console.WriteLine(pi.Name + ": " + pi.ParameterType.ToString());
}

This works pretty well, but it prints out the type of primitives as "System.String" instead of "string" and "System.Nullable`1[System.Int32]" instead of "int?". Is there a way to get the name of the parameter as it looks in code, e.g.

public Example(string p1, int? p2)

prints

p1: string
p2: int?

instead of

p1: System.String
p2: System.Nullable`1[System.Int32]
Jordan
  • 271
  • 3
  • 3
  • 1
    Possible duplicate of [Is there a way to get a type's alias through reflection?](https://stackoverflow.com/questions/1362884/is-there-a-way-to-get-a-types-alias-through-reflection) – Mikael Dúi Bolinder Feb 28 '19 at 07:19

6 Answers6

38

EDIT: I was half wrong in the answer below.

Have a look at CSharpCodeProvider.GetTypeOutput. Sample code:

using Microsoft.CSharp;
using System;
using System.CodeDom;

class Test
{
    static void Main()
    {
        var compiler = new CSharpCodeProvider();
        // Just to prove a point...
        var type = new CodeTypeReference(typeof(Int32));
        Console.WriteLine(compiler.GetTypeOutput(type)); // Prints int
    }
}

However, this doesn't translate Nullable<T> into T? - and I can't find any options which would make it do so, although that doesn't mean such an option doesn't exist :)


There's nothing in the framework to support this - after all, they're C#-specific names.

(Note that string isn't a primitive type, by the way.)

You'll have to do it by spotting Nullable`1 yourself (Nullable.GetUnderlyingType may be used for this, for example), and have a map from the full framework name to each alias.

Jason C
  • 38,729
  • 14
  • 126
  • 182
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • `CSharpCodeProvider.GetTypeOutput` doesn't change `System.String` to `string` though. – Mark Avenius Dec 06 '10 at 18:46
  • it does here now too... I should have known better :-) oh, and I posted that before your code was up – Mark Avenius Dec 06 '10 at 18:52
  • Unforunately it does not work with "out" parameters of methods :( I – Mic Apr 04 '12 at 12:27
  • 1
    @JonSkeet In my case I ended up with something like `(Nullable.GetUnderlyingType(p.ParameterType) != null ? compiler.GetTypeOutput(new System.CodeDom.CodeTypeReference(Nullable.GetUnderlyingType(p.ParameterType))) + "?" : compiler.GetTypeOutput(new System.CodeDom.CodeTypeReference(p.ParameterType)))`, using the original type to check for nullable – Dennis van Gils Mar 15 '19 at 08:55
  • Also, should the CSharpCodeProvider be wrapped in a using statement? Seeing as it's IDisposable – Dennis van Gils Mar 15 '19 at 08:57
  • 1
    @DennisvanGils: I believe that's just inherited from `Component`. I suspect that in most cases it's fine not to dispose it - but it wouldn't hurt to do so. – Jon Skeet Mar 15 '19 at 09:04
  • 1
    @Mic: If you want it to work with in/out/ref params, run it through a pass of `if (type.HasElementType) type = type.GetElementType();` first (you may want to give array types special treatment here, though). If you're also resolving nullables, do that *before* `Nullable.GetUnderlyingType`. – Jason C Jun 11 '21 at 04:26
4

This question has two interesting answers. The accepted one from Jon Skeet pretty much says what he said already.

EDIT Jon updated his answer so its pretty much the same as mine is now. (But of course 20 secs earlier)

But Luke H also gives this answer which I thought was pretty awesome use of the CodeDOM.

Type t = column.DataType;    // Int64

StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
    var expr = new CodeTypeReferenceExpression(t);

    var prov = new CSharpCodeProvider();
    prov.GenerateCodeFromExpression(expr, sw, new CodeGeneratorOptions());
}

Console.WriteLine(sb.ToString());    // long
Community
  • 1
  • 1
Conrad Frix
  • 51,984
  • 12
  • 96
  • 155
1

Not the most beautiful code in the world, but this is what I ended up doing: (building on Cornard's code)

public static string CSharpName(this Type type)
{
    if (!type.FullName.StartsWith("System"))
        return type.Name;
    var compiler = new CSharpCodeProvider();
    var t = new CodeTypeReference(type);
    var output = compiler.GetTypeOutput(t);
    output = output.Replace("System.","");
    if (output.Contains("Nullable<"))
        output = output.Replace("Nullable","").Replace(">","").Replace("<","") + "?";
    return output;
}
K. R.
  • 1,220
  • 17
  • 20
1

Another option, based on the other answers here.

Features:

  • Convert String to string and Int32 to int etc
  • Deal with Nullable<Int32> as int? etc
  • Suppress System.DateTime to be DateTime
  • All other types are written in full

It deals with the simple cases I needed, not sure if it will handle complex types well..

Type type = /* Get a type reference somehow */
var compiler = new CSharpCodeProvider();
if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
    return compiler.GetTypeOutput(new CodeTypeReference(type.GetGenericArguments()[0])).Replace("System.","") + "?";
}
else
{
    return compiler.GetTypeOutput(new CodeTypeReference(type)).Replace("System.","");
}   
user230910
  • 2,353
  • 2
  • 28
  • 50
0

No. string is just a representation of System.String -- string doesn't really mean anything behind the scenes.

By the way, to get past System.Nullable'1[System.Int32], you can use Nullable.GetUnderlyingType(type);

Mark Avenius
  • 13,679
  • 6
  • 42
  • 50
-2

Here's what I came up with after ~5 mins of hacking. For example:

CSharpAmbiance.GetTypeName(typeof(IDictionary<string,int?>))

will return System.Collections.Generic.IDictionary<string, int?>.

public static class CSharpAmbiance
{
    private static Dictionary<Type, string> aliases =
        new Dictionary<Type, string>();

    static CSharpAmbiance()
    {
        aliases[typeof(byte)] = "byte";
        aliases[typeof(sbyte)] = "sbyte";
        aliases[typeof(short)] = "short";
        aliases[typeof(ushort)] = "ushort";
        aliases[typeof(int)] = "int";
        aliases[typeof(uint)] = "uint";
        aliases[typeof(long)] = "long";
        aliases[typeof(ulong)] = "ulong";
        aliases[typeof(char)] = "char";

        aliases[typeof(float)] = "float";
        aliases[typeof(double)] = "double";

        aliases[typeof(decimal)] = "decimal";

        aliases[typeof(bool)] = "bool";

        aliases[typeof(object)] = "object";
        aliases[typeof(string)] = "string";
    }

    private static string RemoveGenericNamePart(string name)
    {
        int backtick = name.IndexOf('`');

        if (backtick != -1)
            name = name.Substring(0, backtick);

        return name;
    }

    public static string GetTypeName(Type type)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        string keyword;
        if (aliases.TryGetValue(type, out keyword))
            return keyword;

        if (type.IsArray) {
            var sb = new StringBuilder();

            var ranks = new Queue<int>();
            do {
                ranks.Enqueue(type.GetArrayRank() - 1);
                type = type.GetElementType();
            } while (type.IsArray);

            sb.Append(GetTypeName(type));

            while (ranks.Count != 0) {
                sb.Append('[');

                int rank = ranks.Dequeue();
                for (int i = 0; i < rank; i++)
                    sb.Append(',');

                sb.Append(']');
            }

            return sb.ToString();
        }

        if (type.IsGenericTypeDefinition) {
            var sb = new StringBuilder();

            sb.Append(RemoveGenericNamePart(type.FullName));
            sb.Append('<');

            var args = type.GetGenericArguments().Length - 1;
            for (int i = 0; i < args; i++)
                sb.Append(',');

            sb.Append('>');

            return sb.ToString();
        }

        if (type.IsGenericType) {
            if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
                return GetTypeName(type.GetGenericArguments()[0]) + "?";

            var sb = new StringBuilder();

            sb.Append(RemoveGenericNamePart(type.FullName));
            sb.Append('<');

            var args = type.GetGenericArguments();
            for (int i = 0; i < args.Length; i++) {
                if (i != 0)
                    sb.Append(", ");

                sb.Append(GetTypeName(args[i]));
            }

            sb.Append('>');

            return sb.ToString();
        }

        return type.FullName;
    }
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • What about arrays? Should a two-dimensional array of int be "int[,]" or "System.Int32[,]", or what? – Eric Lippert Dec 06 '10 at 19:49
  • @Eric: Oops, forgot about array types. I have updated the method to handle them. – cdhowie Dec 06 '10 at 20:01
  • Your code is now wrong for ragged array types. Remember, an int[,][] is a two-dimensional array of one-dimensional arrays, not a one-dimensional array of two-dimensional arrays. – Eric Lippert Dec 06 '10 at 21:05
  • @Eric: Ah, yes, one of the reasons I hate C# array syntax. One would expect `int[,][]` to mean `(int[,])[]`... – cdhowie Dec 06 '10 at 21:08
  • 2
    See my article on the subject for an explanation of why it is this way: http://blogs.msdn.com/b/ericlippert/archive/2009/08/17/arrays-of-arrays.aspx – Eric Lippert Dec 06 '10 at 21:41
  • @Eric: Are you trying to make a point or do you actually want to see the code improved? – cdhowie Dec 06 '10 at 21:48
  • 9
    Both. If you're going to proffer a solution to a problem then it's worthwhile to actually solve the stated problem, and actively harmful to provide a half-baked solution that solves the stated problem wrong in many cases. The larger point is that your design methodology leaves much to be desired; rather than writing a bad solution and then adding a patch every time someone finds a bug, write a spec that considers all the cases *first* and then code to the spec. – Eric Lippert Dec 06 '10 at 21:54
  • 1
    Some of the other cases you've forgotten, incidentally, are pointer types, out/ref types, and the fact that System.Void is aliased as "void". – Eric Lippert Dec 06 '10 at 21:55
  • @Eric: And you didn't tell me that initially why? P.S. Still waiting for your solution. – cdhowie Dec 06 '10 at 21:59
  • 1
    @Eric: I guess my point is that you could have said that right away and saved a lot of pointless re-coding. I would rather have you be up-front with your criticism than drag me through a bunch of pointless exercises -- you are not my professor, so please don't act like you are. My answer has been community-wikified. Feel free to direct some of your energy into improving it if you would like. – cdhowie Dec 06 '10 at 22:05
  • 5
    Because the dialectical method is a highly effective pedagogic technique. I don't think it was pointless re-coding if you, or someone else, learned something valuable from it. My solution is not forthcoming; a correct solution would take me at least half the day to write and test and I don't have that kind of time. – Eric Lippert Dec 06 '10 at 22:08
  • 1
    @Eric: It's also incredibly condescending if we are not in an existing student-teacher relationship. – cdhowie Dec 06 '10 at 22:09
  • 3
    @cdhowie: Keep in mind he's trying to teach anyone else who reads this in the future as well, so is probably not a simple attempt at a personal insult. – asawyer Dec 06 '10 at 22:23
  • @asawyer: I'm not saying it was an *attempt* to insult, only that it is a rather insulting approach without any preexisting relationship between us. – cdhowie Dec 06 '10 at 22:25
  • 14
    @asawyer: It is not any kind of attempt at any kind of insult, personal or otherwise. It's extremely constructive and well-meant feedback on a deeply flawed answer to a reasonable technical question. This is a collaberatively edited site which seeks to provide accurate answers to technical questions; constructive feedback is by design a part of that. – Eric Lippert Dec 06 '10 at 22:28