Nullable type is not a nullable type?
I was doing some testing with nullable types, and it didn't work quite as I expected:
int? testInt = 0;
Type nullableType = typeof(int?);
Assert.AreEqual(nullableType, testInt.GetType()); // not the same type
This doesn't work either:
DateTime? test = new DateTime(434523452345);
Assert.IsTrue(test.GetType() == typeof(Nullable)); //FAIL
DateTime? test = new DateTime(434523452345);
Assert.IsTrue(test.GetType() == typeof(Nullable<>)); //STILL FAIL
My question is why does testInt.GetType() return int, and typeof(int?) return the true nullable type?
According to the MSDN :
Calling GetType on a Nullable type causes a boxing operation to be performed when the type is implicitly converted to Object. Therefore GetType always returns a Type object that represents the underlying type, not the Nullable type.
When you box a nullable object, only the underlying type is boxed.
Again, from MSDN :
Boxing a non-null nullable value type boxes the value type itself, not the System.Nullable that wraps the value type.
Further to Romain's correct answer, if you want to compare the "real" types (ie, without implicitly converting any nullable type to its underlying type) then you can create an extension method like so:
public static class MyExtensionMethods
{
public static Type GetRealType<T>(this T source)
{
return typeof(T);
}
}
And then try the following tests:
int? a = 0;
Console.WriteLine(a.GetRealType() == typeof(int?)); // True
Console.WriteLine(a.GetRealType() == typeof(int)); // False
int b = 0;
Console.WriteLine(b.GetRealType() == typeof(int)); // True
Console.WriteLine(b.GetRealType() == typeof(int?)); // False
DateTime? c = DateTime.Now;
Console.WriteLine(c.GetRealType() == typeof(DateTime?)); // True
Console.WriteLine(c.GetRealType() == typeof(DateTime)); // False
DateTime d = DateTime.Now;
Console.WriteLine(d.GetRealType() == typeof(DateTime)); // True
Console.WriteLine(d.GetRealType() == typeof(DateTime?)); // False
EDIT...
For completeness -- and prompted by SLaks's comments below -- here's an alternative version that only uses the compile-time type when source
is either null
or Nullable<>
; otherwise it uses GetType
and returns the runtime type:
public static class MyExtensionMethods
{
public static Type GetRealType<T>(this T source)
{
Type t = typeof(T);
if ((source == null) || (Nullable.GetUnderlyingType(t) != null))
return t;
return source.GetType();
}
}
Although C# pretends that value-type storage locations hold instances of types derived from System.ValueType
, which in turn derives from System.Object
, that isn't really true. Each type derived from System.ValueType
actually represents two very different kinds of things:
- A collection of bytes which (for primitive types) represents the data directly, or (for non-primitive structure types) holds the contents of all fields, public and private, but does not hold any type information.
- A standalone heap object, which contains an object header in addition to the above, whose type is derived from `System.ValueType`.
Storage locations of a value type hold the first; heap objects of a value type hold the second.
For various reasons, Microsoft decided that Nullable<T>
should only support the first usage. If one attempts to pass a storage location of type Nullable<T>
to code which expects a reference to a heap object, the system will convert the item to a T
if HasValue
is true, or else simply pass a null reference if HasValue
is false. While there are ways to create a heap object of type Nullable<T>
, the normal methods of converting a value-type storage location to a heap object will never generate one.
Note also that calling GetType()
on a value storage location won't actually evaluate the type of the storage location, but will instead convert the contents of that storage location to a heap object and then return the type of the resulting object. Because storage locations of type Nullable<T>
get converted either to object instances of T
or to null, nothing in an object instance will say whether the storage location from which it came was a Nullable<T>
.