Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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:

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

A simple way to check that is using the "is" operator:

(i is Nullable<int>) || (i is Nullable<long>) || (i is Nullable<float>) || (i is Nullable<short>)

I figured out ti reading these two MSDN pages:

http://msdn.microsoft.com/en-us/library/ms366789(v=vs.90).aspx

http://msdn.microsoft.com/en-us/library/ms228597%28v=VS.90%29.aspx

Cheers!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top