Can someone explain why is it possible to call methods on null
instance?
int? num = null;
var s1 = num.ToString();
var s2 = num.HasValue;
var s3 = num.GetHashCode();
var s4 = num.GetValueOrDefault();
var s5 = num.Value; // InvalidOperationException
var s6 = num.GetType(); // NullReferenceException
I can check the num
is null
in a debug mode, so how is it possible that ToString
method or HasValue
getter can be called on null
instance, but for Value
and GetType
it is not possible? All of them are methods or properties on Nullable<>
type or not?
I would naturally expect the Value
getter returns null
value, similarly as the HasValue
returns false
. I would expect too, that GetType
returns Nullable<int>
type info, or num is int?
or num is Nullable<int>
works. Why doesn't it work? How can I check the num
is a nullable type?
Creating an instance doesn't change anything on that:
Nullable<int> num = new Nullable<int>();
What's behind the scene?
Nullable<T>
has a bit of compiler magic that makes it appear as if there was a null
value. But there isn't. Basically, since Nullable<T>
is a value type, it cannot be null
to begin with. Its nullability is determined whether there is a value or not. This means that you can call HasValue
(which is important, since that's what the compiler inserts when you write num == null
) and other methods that don't depend on a value being present.
As for a few specific points:
ToString
is an implementation that works similar to how null
values are converted to string when used in string concatenation, i.e. resulting in an empty string. You also don't really want ToString
to ever throw.GetHashCode
is necessary to put Nullable<T>
as a key in a dictionary, or place them into a hashset. It also should not throw, so it has to return something sensible when there's no value.Accessing the value when there isn't one is forbidden, though. As Zohar Peled and Marc Gravell ♦ note in the comments, this is what implicitly happens when calling GetType
:
It would seem logical that the compiler could just silently replace
nullableValue.GetType()
withtypeof(SomeT?)
, but that would then mean that it always gives confusing answers when compared toobject obj = nullableValue; Type type = obj.GetType()
You would expect this to work similarly, but
obj.GetType()
will always return eithertypeof(T)
(nottypeof(T?)
), or throw aNullReferenceException
becauseT?
boxes to either aT
or tonull
(and you can't ask anull
what type it is)
The mapping of constructs the compiler handles specially is more of less the following:
num == null → !num.HasValue
num != null → num.HasValue
num = null → num = new Nullable<int>()
num = 5 → num = new Nullable<int>(5)
(int) num → num.Value
(object) num → (object) num.Value // if HasValue
→ (object) null // if !HasValue
There's additional support for operators, most importantly comparison operators with non-nullable T
s and the various operators that work on potential null
values like ??
, but that's the gist of it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With