5

GetType().ToString() returns the FullName of the object. I want the name that you usually use to instantiate that object, i.e, int instead of Int32. Is there a way to do this?

user2793618
  • 305
  • 4
  • 10
  • Why? anyway you cant... You will have to build a *Dictionary* and map them over yourself. There is no *reflection* way to get a type *alias*... eg `int` instead of `Int32`, as `Int32` is the actual *type* not `int` which is the *alias* – TheGeneral May 29 '19 at 02:30
  • 1
    It's worth noting that not all .NET assemblies are written in c#. If yours wasn't, the variable of interest may not have been declared as an `int`, which is a c# keyword (in Visual Basic it is `Integer`, for example). The string `Int32` is the .NET name for the type agnostic of language. – John Wu May 29 '19 at 02:45

3 Answers3

14

C# has a number of 'types' that are actually keyword aliases to .NET CLR Types. In this case, int is a C# alias for System.Int32, but the same is true of other C# types like string which is an alias to System.String.

This means that when you get under the hood with reflection and start looking at the CLR Type objects you won't find int, string or any of the other C# type aliases because .NET and the CLR don't know about them... and nor should they.

If you want to translate from the CLR type to a C# alias you'll have to do it yourself via lookup. Something like this:

// This is the set of types from the C# keyword list.
static Dictionary<Type, string> _typeAlias = new Dictionary<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(object), "object" },
    { typeof(sbyte), "sbyte" },
    { typeof(short), "short" },
    { typeof(string), "string" },
    { typeof(uint), "uint" },
    { typeof(ulong), "ulong" },
    // Yes, this is an odd one.  Technically it's a type though.
    { typeof(void), "void" }
};

static string TypeNameOrAlias(Type type)
{
    // Lookup alias for type
    if (_typeAlias.TryGetValue(type, out string alias))
        return alias;

    // Default to CLR type name
    return type.Name;
}

For simple types that will work fine. Generics, arrays and Nullable take a bit more work. Arrays and Nullable values are handled recursively like this:

static string TypeNameOrAlias(Type type)
{
    // Handle nullable value types
    var nullbase = Nullable.GetUnderlyingType(type);
    if (nullbase != null)
        return TypeNameOrAlias(nullbase) + "?";

    // Handle arrays
    if (type.BaseType == typeof(System.Array))
        return TypeNameOrAlias(type.GetElementType()) + "[]";

    // Lookup alias for type
    if (_typeAlias.TryGetValue(type, out string alias))
        return alias;

    // Default to CLR type name
    return type.Name;
}

This will handle things like:

Console.WriteLine(TypeNameOrAlias(typeof(int?[][])));

Generics, if you need them, are a bit more involved basically the same process. Scan through the generic parameter list and run the types recursively through the process.


Nested Types

When you run TypeNameOrAlias on a nested type the result is only the name of the specific type, not the full path you'd need to specify to use it from outside the type that declares it:

public class Outer
{
    public class Inner
    {
    }
}
// TypeNameOrAlias(typeof(Outer.Inner)) == "Inner"

This resolves the issue:

static string GetTypeName(Type type)
{
    string name = TypeNameOrAlias(type);
    if (type.DeclaringType is Type dec)
    {
        return $"{GetTypeName(dec)}.{name}";
    }
    return name;
}
// GetTypeName(typeof(Outer.Inner)) == "Outer.Inner"

Generics

Generics in the .NET type system are interesting. It's relatively easy to handle things like List<int> or Dictionary<int, string> or similar. Insert this at the top of TypeNameOrAlias:

    // Handle generic types
    if (type.IsGenericType)
    {
        string name = type.Name.Split('`').FirstOrDefault();
        IEnumerable<string> parms = 
            type.GetGenericArguments()
            .Select(a => type.IsConstructedGenericType ? TypeNameOrAlias(a) : a.Name);
        return $"{name}<{string.Join(",", parms)}>";
    }

Now you'll get correct results for things like TypeNameOrAlias(typeof(Dictionary<int, string>)) and so on. It also deals with generic type definitions: TypeNameOrAlias(typeof(Dictionary<,>)) will return Dictionary<TKey,TValue>.

Where things get difficult is when you nest classes inside generics. Try GetTypeName(typeof(Dictionary<int, string>.KeyCollection)) for an interesting result.

Corey
  • 15,524
  • 2
  • 35
  • 68
3
GetType().ToString().FromDotNetTypeToCSharpType();

Using the extension method below. I came up with something similar for some templating I was doing in c# using the following Microsoft reference:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table

Optionally, a boolean whether the type is nullable can be passed in for the shortcut nullable syntax.

    /// <summary>Converts a .Net type name to a C# type name. It will remove the "System." namespace, if present,</summary>
    public static string FromDotNetTypeToCSharpType(this string dotNetTypeName, bool isNull = false)
    {
        string cstype = "";
        string nullable = isNull ? "?" : "";
        string prefix = "System.";
        string typeName = dotNetTypeName.StartsWith(prefix) ? dotNetTypeName.Remove(0, prefix.Length) : dotNetTypeName;

        switch (typeName)
        {
            case "Boolean": cstype = "bool"; break;
            case "Byte":    cstype = "byte"; break;
            case "SByte":   cstype = "sbyte"; break;
            case "Char":    cstype = "char"; break;
            case "Decimal": cstype = "decimal"; break;
            case "Double":  cstype = "double"; break;
            case "Single":  cstype = "float"; break;
            case "Int32":   cstype = "int"; break;
            case "UInt32":  cstype = "uint"; break;
            case "Int64":   cstype = "long"; break;
            case "UInt64":  cstype = "ulong"; break;
            case "Object":  cstype = "object"; break;
            case "Int16":   cstype = "short"; break;
            case "UInt16":  cstype = "ushort"; break;
            case "String":  cstype = "string"; break;

            default: cstype = typeName; break; // do nothing
        }
        return $"{cstype}{nullable}";

    }
WillC
  • 1,761
  • 17
  • 37
3

This uses CSharpCodeProvider, handles generics and adds namespaces when required.

using System;
using System.CodeDom;
using System.Collections.Generic;
using Microsoft.CSharp;
//...
private string GetFriendlyTypeName(Type type)
{
    using (var p = new CSharpCodeProvider())
    {
        var r = new CodeTypeReference(type);
        return p.GetTypeOutput(r);
    }
}

Giving credit where it's due.

xvan
  • 4,554
  • 1
  • 22
  • 37