Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Methods on Nullable types

Tags:

c#

nullable

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?

like image 892
quantumbit Avatar asked Mar 05 '23 14:03

quantumbit


1 Answers

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.
  • The documentation explains a bit of the underlying concepts.

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() with typeof(SomeT?), but that would then mean that it always gives confusing answers when compared to

object obj = nullableValue;
Type type = obj.GetType()

You would expect this to work similarly, but obj.GetType() will always return either typeof(T) (not typeof(T?)), or throw a NullReferenceException because T? boxes to either a T or to null (and you can't ask a null 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 Ts and the various operators that work on potential null values like ??, but that's the gist of it.

like image 105
Joey Avatar answered Mar 17 '23 01:03

Joey