Nullable type is not a nullable type?
Asked Answered
S

4

50

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?

Stephenson answered 24/4, 2009 at 10:44 Comment(0)
F
58

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.

Foofaraw answered 24/4, 2009 at 10:46 Comment(0)
M
22

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();
    }
}
Monasticism answered 24/4, 2009 at 12:14 Comment(6)
Note that this gets the compile-time type.Geralyngeraniaceous
@SLaks: Is there ever an occasion where the runtime type of a nullable differs from the compile-time type? (I appreciate that you could pass any type to this method, in which case you would indeed get the runtime type, but its intended use is to get the "real" type of a nullable, as per the question.)Monasticism
You're right. ((int?)2).GetRealType() doesn't really count. ((object)nullable).GetRealType() is ambiguous.Geralyngeraniaceous
@SLaks: Oops, what I meant to say was "...in which case you would indeed get the compile-time type". And, yes, when you call ((object)nullable).GetRealType() you're no longer passing a nullable at all. You're either passing a boxed value of the nullable's underlying type, or just plain null.Monasticism
You could mention that a Nullable<T> variable must not be boxed in order to work with your second version. Also renaming your first version to GetCompileType would make it clear what this method really does.Brazenfaced
This doesn't work with subclasses, though. You will get the type of the reference, not the type of the object. int? a = -5; object o = a; a.GetRealType() will return "object", not "int?"Monck
J
3

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

Jerky answered 2/4, 2012 at 20:23 Comment(0)
S
0

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!

Secundine answered 12/11, 2013 at 12:38 Comment(1)
Doesn't scale very well, does it? Nullable<Enum1>, Nullable<Enum2>, etc, etcMonck

© 2022 - 2024 — McMap. All rights reserved.