Something just occurred to me earlier today that has got me scratching my head.
Any variable of type Nullable<T>
can be assigned to null
. For instance:
int? i = null;
At first I couldn't see how this would be possible without somehow defining an implicit conversion from object
to Nullable<T>
:
public static implicit operator Nullable<T>(object box);
But the above operator clearly does not exist, as if it did then the following would also have to be legal, at least at compile-time (which it isn't):
int? i = new object();
Then I realized that perhaps the Nullable<T>
type could define an implicit conversion to some arbitrary reference type that can never be instantiated, like this:
public abstract class DummyBox
{
private DummyBox()
{ }
}
public struct Nullable<T> where T : struct
{
public static implicit operator Nullable<T>(DummyBox box)
{
if (box == null)
{
return new Nullable<T>();
}
// This should never be possible, as a DummyBox cannot be instantiated.
throw new InvalidCastException();
}
}
However, this does not explain what occurred to me next: if the HasValue
property is false
for any Nullable<T>
value, then that value will be boxed as null
:
int? i = new int?();
object x = i; // Now x is null.
Furthermore, if HasValue
is true
, then the value will be boxed as a T
rather than a T?
:
int? i = 5;
object x = i; // Now x is a boxed int, NOT a boxed Nullable<int>.
But this seems to imply that there is a custom implicit conversion from Nullable<T>
to object
:
public static implicit operator object(Nullable<T> value);
This is clearly not the case as object
is a base class for all types, and user-defined implicit conversions to/from base types are illegal (as well they should be).
It seems that object x = i;
should box i
like any other value type, so that x.GetType()
would yield the same result as typeof(int?)
(rather than throw a NullReferenceException
).
So I dug around a bit and, sure enough, it turns out this behavior is specific to the Nullable<T>
type, specially defined in both the C# and VB.NET specifications, and not reproducible in any user-defined struct
(C#) or Structure
(VB.NET).
Here's why I'm still confused.
This particular boxing and unboxing behavior appears to be impossible to implement by hand. It only works because both C# and VB.NET give special treatment to the Nullable<T>
type.
Isn't it theoretically possible that a different CLI-based language could exist where
Nullable<T>
weren't given this special treatment? And wouldn't theNullable<T>
type therefore exhibit different behavior in different languages?How do C# and VB.NET achieve this behavior? Is it supported by the CLR? (That is, does the CLR allow a type to somehow "override" the manner in which it is boxed, even though C# and VB.NET themselves prohibit it?)
Is it even possible (in C# or VB.NET) to box a
Nullable<T>
asobject
?