Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When exactly do nullable types throw exceptions?

Consider the following code:

int? x = null; Console.Write ("Hashcode: "); Console.WriteLine(x.GetHashCode()); Console.Write("Type: "); Console.WriteLine(x.GetType()); 

When executed, it writes that Hashcode is 0, but fails with NullReferenceException in attempt to determine type of x. I know that methods called on nullable types are actually called on underlying values, so I expected program to fail during x.GetHashCode().

So, what is the fundamental difference between those two methods and why doesn't the first of them fail?

like image 671
Кирилл Глазырин Avatar asked May 02 '18 06:05

Кирилл Глазырин


People also ask

Should you throw null reference exception?

It's usually better to avoid a NullReferenceException than to handle it after it occurs. Handling an exception can make your code harder to maintain and understand, and can sometimes introduce other bugs. A NullReferenceException is often a non-recoverable error.

Which statement is true about nullable type?

Characteristics of Nullable TypesNullable types can only be used with value types. The Value property will throw an InvalidOperationException if value is null; otherwise it will return the value. The HasValue property returns true if the variable contains a value, or false if it is null. You can only use == and !=

What is the point of nullable reference types?

Nullable reference types are a new feature in C# 8.0. They allow you to spot places where you're unintentionally dereferencing a null value (or not checking it.) You may have seen these types of checks being performed before C# 8.0 in ReSharper's Value and Nullability Analysis checks.

What happens when we box or unbox nullable types?

When the nullable type is boxed, the underlying value type is stored in the object, rather than an instance of the nullable type itself. For example, if we box int?, the boxed value will store an int.


2 Answers

This is because int? x = null; essentially creates an instance of the value type System.Nullable<int>, with an "inner" null value (you can check it via .HasVaue Property). When GetHashCode is invoked, the override Nullable<int>.GetHashCode is the method candidate (since the method is virtual), now we have an instance of Nullable<int>, and execute its instance method, perfect.

When invoking GetType, the method is non-virtual, so the instance of Nullable<int> is boxed to System.Object first, according to the document, and boxed value is null, hence the NullReferenceException.

like image 62
Cheng Chen Avatar answered Oct 04 '22 05:10

Cheng Chen


To clarify Danny Chen's correct answer:

  • Nullable<T> is a value type. The value type consists of a bool, which indicates nullity (false means null) and a T, the value.
  • Unlike all other value types, nullable types do not box to a boxed Nullable<T>. They box to either a boxed T or a null reference.
  • A method implemented by a value type S is implemented as though it has an invisible ref S argument; that is how this is passed.
  • A method implemented by a reference type C is implemented as if there was an invisible C argument; that is how this is passed.
  • The interesting case is then a virtual method defined in a reference base class and overridden by a struct that inherits from the base class.

Now you have enough information to deduce what happens. GetHashCode is virtual and overridden by Nullable<T> so when you call it, you call it as though there was an invisible ref Nullable<T> argument for this. No boxing happens.

GetType is not virtual and so cannot be overridden and is defined on object. Therefore it expects an object for this, When called on a Nullable<T> the receiver must be boxed, and therefore can box to null, and therefore can throw.

If you called ((object)x).GetHashCode() then you'd see an exception.

like image 40
Eric Lippert Avatar answered Oct 04 '22 04:10

Eric Lippert