As others have said, "?" is just shorthand for changing it to Nullable<T>
. This is just another value type with a Boolean flag to say whether or not there's really a useful value, or whether it's the null value for the type. In other words, Nullable<T>
looks a bit like this:
public struct Nullable<T>
{
private readonly bool hasValue;
public bool HasValue { get { return hasValue; } }
private readonly T value;
public T value
{
get
{
if (!hasValue)
{
throw new InvalidOperationException();
}
return value;
}
}
public Nullable(T value)
{
this.value = value;
this.hasValue = true;
}
// Calling new Nullable<int>() or whatever will use the
// implicit initialization which leaves value as default(T)
// and hasValue as false.
}
Obviously in the real code there are more methods (like GetValueOrDefault()
) and conversion operators etc. The C# compiler adds lifted operators which effectively proxy to the original operators for T
.
At the risk of sounding like a broken record, this is still a value type. It doesn't involve boxing... and when you write:
int? x = null;
that's not a null reference - it's the null value of Nullable<int>
, i.e. the one where hasValue
is false
.
When a nullable type is boxed, the CLR has a feature whereby the value either gets boxed to a null reference, or a plain boxed T. So if you have code like this:
int? x = 5;
int y = 5;
object o1 = x;
object o2 = y;
The boxed values referred to by o1
and o2
are indistinguishable. You can't tell that one is the result of boxing a nullable type.