You might have already read this: VS2005 made the last-minute DCR related to boxed Nullable<T>. Runtime now treats Nullable<T> differently from other generic value types when boxing:

Int32? x = null;
object y = x; 
// y is simply null, not a truly boxed "nullable<int> struct" (which then tells it has no value)


Int32 ? x = 100;
object y = x; 
// y is boxed Int32 100 (same as "object y = 100"), no longer a boxed "nullable<int> struct" with a field of value 100.

Reflection late-bind invocation always deals with "object"; value type instances are always getting boxed first before invocation. One of such APIs: PropertyInfo.SetValue(Object obj, Object value, Object[] index). There was one question in Microsoft Technical Forums: Exception is thrown when setting DateTime value to a property with type Nullable<DateTime>. With this DCR change, such late-bind calls can be written in a more natural way.

class C
   Int32? m_field;
   public Int32? Property
    get { return m_field; }
    set { m_field = value; }

PropertyInfo pi = typeof(C).GetProperty("Property");
C c = new C();
pi.SetValue(c, 100, null);         // works
pi.SetValue(c, (Int32?)100, null); // no longer have to write like this

After boxing, 100 and (Int32?)100 are the same thing: boxed Int32 object. There is no more boxed object with type "Int32?"; late-invocation has to accept it and set to Int32? property. Or think it this way, runtime can not tell where a boxed Int32 object was originally from: either just Int32 instance or Int32? instance; it is reasonable to allow setting a boxed Int32 object to field/property of either Int32 or Int32? type (same for argument pass-in when doing method invocation)

pi.SetValue(c, null, null);        // works

This actually worked before the DCR. And ,

Object ret = pi.GetValue(c, null);

ret will be either boxed Int32 object or simply null. There is no problem in casting the result to Int32? type.

Here are some reflection behavior changes as a result of this DCR:

  • Probably the most important one is "typeof(T?).IsAssignableFrom(typeof(T)) == true". PropertyInfo.SetValue/GetValue use this first to ensure the pass-in value is assignable to Nullable<T> property.
  • Object.GetType() will never get back any type of Nullable<T>. Joe had an interesting post on this.
  • Activator.CreateInstance could never be expected to return null before; with this DCR, it will return null when creating instance of type Nullable<T> but not providing non-null T value. For example, Activator.CreateInstance(typeof(Int32?)) returns null.