Why Gettype Returns System.Int32 Instead of Nullable<Int32>

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 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.

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 is false, the object reference is assigned to null instead of boxing

If the object is non-null -- if HasValue is true -- 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 the System.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 Types 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



Leave a reply



Submit