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?
-
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
-
1It'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 Answers
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.
- 15,524
- 2
- 35
- 68
-
Thanks for the detailed response! Why are non simple types handled recursively? – user2793618 May 29 '19 at 15:13
-
1Recursion is required because you can't know how deep the tree goes before you get to a simple type that you want to alias. The example `int?[][]` is 3 levels of recursion to get to the `int`. – Corey May 30 '19 at 01:02
-
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}";
}
- 1,761
- 17
- 37
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.
- 4,554
- 1
- 22
- 37