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