Why GetType returns System.Int32 instead of NullableInt32?
GetType()
is a method of object
.
To call it, the Nullable<T>
struct must be boxed.
You can see this in the IL code:
//int? x = 5;
IL_0000: ldloca.s 00
IL_0002: ldc.i4.5
IL_0003: call System.Nullable<System.Int32>..ctor
//Console.WriteLine(x.GetType());
IL_0008: ldloc.0
IL_0009: box System.Nullable<System.Int32>
IL_000E: callvirt System.Object.GetType
IL_0013: call System.Console.WriteLine
Nullable types are treated specially by CLR; it is impossible to have a boxed instance of a nullable type.
Instead, boxing a nullable type will result in a null reference (if HasValue
is false), or the boxed value (if there is a value).
Therefore, the box System.Nullable<System.Int32>
instruction results in a boxed Int32
, not a boxed Nullable<Int32>
.
Therefore, it is impossible for GetType()
to ever return Nullable<T>
.
To see this more clearly, look at the following code:
static void Main()
{
int? x = 5;
PrintType(x);
}
static void PrintType<T>(T val) {
Console.WriteLine("Compile-time type: " + typeof(T));
Console.WriteLine("Run-time type: " + val.GetType());
}
This prints
Compile-time type: System.Nullable`1[System.Int32]
Run-time type: System.Int32
GetType return Int instead of System.Int32
C# has a number of 'types' that are actually keyword aliases to .NET CLR Type
s. 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.
Whats the use of Nullable.GetUnderlyingType, if typeof(int?) is an Int32?
Calling GetType()
boxes your variable. The CLR has a special rule that Nullable<T>
gets boxed to T
. So x.GetType
will return Int32
instead of Nullable<Int32>
.
int? x = 1;
x.GetType() //Int32
typeof(int?) //Nullable<Int32>
Since a Nullable
containing null
will be boxed to null
the following will throw an exception:
int? x = null;
x.GetType() //throws NullReferenceException
To quote MSDN on Boxing Nullable Types:
Objects based on nullable types are only boxed if the object is non-null. If
HasValue
isfalse
, the object reference is assigned tonull
instead of boxingIf the object is non-null -- if
HasValue
istrue
-- then boxing occurs, but only the underlying type that the nullable object is based on is boxed. Boxing a non-null nullable value type boxes the value type itself, not theSystem.Nullable<T>
that wraps the value type.
Nullable, check for default
You can't do this, because when you call the method, the nullable struct is boxed to be object
, and becomes either null
or an Int32
value. This is explained in an earlier answer: Why GetType returns System.Int32 instead of Nullable<Int32>?
If your variable is typed as an object in the place where this method is called, I don't think you can get the information you need - it is already boxed.
How to get a NullableT instance's type
Not elegant, but you can wrap it in a generic function:
public static void Main()
{
int? i = 2;
Console.WriteLine( type(i) );
}
public static Type type<T>( T x ) { return typeof(T); }
C# Reflection: How to get the type of a Nullableint?
Update: Looks like C# 7 will support switching on Type
s as the asker of this question was trying to do. It's a little different though so watch out for syntax landmines.
You don't need a string name to compare it:
if (myObject.GetType().GetProperty("id").PropertyType == typeof(Nullable<Int32>))
// when Nullable<Int32>, do this
else if (myObject.GetType().GetProperty("id").PropertyType == typeof(string))
// when string, do this
else if (myObject.GetType().GetProperty("id").PropertyType == typeof(Nullable<bool>))
// when Nullable<bool>, do this
Related Topics
Set Item Focus in Listview Wpf
System.Io.Filestream Fileaccess VS Fileshare
How to Change Currentculture at Runtime
Converting Long String of Binary to Hex C#
C# - How to Detect a Windows Shutdown/Logoff and Cancel That Action (After Asking the User)
How to Write a Comment to an Xml File When Using the Xmlserializer
Selectively Coloring Text in Richtextbox
How to Access Page Controls Inside a Static Web Method
How to Read to End Process Output Asynchronously in C#
Multiple Controller Types with Same Route Prefix ASP.NET Web API
How to Register Windows Forms with Simple Injector
How to Read the Current Path of |Datadirectory| from Config Settings
Wpf Clickonce Dpi Awareness Per-Monitor V2