C# Safe navigation operator - what is actually going on?

recursive picture recursive · Aug 28, 2015 · Viewed 15.3k times · Source

I've been following the safe navigation operator feature added in C#6 with some interest. I've been looking forward to it for a while. But I'm finding some different behavior than I expected. I'm realizing I really don't understand how it actually works.

Given this class

class Foo {
    public int? Measure;
}

Here's some code using the new operator.

Foo f = new Foo { Measure = 3 };
Console.WriteLine(f?.Measure);  // 3

f = new Foo { Measure = null };
Console.WriteLine(f?.Measure);  // null

f = null;
Console.WriteLine(f?.Measure);  // null

Up to here, everything's working as expected. ?. is accessing members when the left hand side is not null, otherwise returning null. But here things go in a direction I wasn't expecting.

var i = f?.Measure; // i is Nullable<int>
Console.WriteLine(i.HasValue); // false
Console.WriteLine(f?.Measure.HasValue); // null

What?

Why can I get HasValue from i, but not from the same expression I assigned to i? How can HasValue ever be null?

Edit: My real question is about program behavior, not a compilation error. I removed the extra stuff about compilation, and focused this question more narrowly on why two different results are returned by what seems like the same logic.

Answer

Rob picture Rob · Aug 28, 2015

Let's walk through this logically.

var f = ???;
var i = f?.Measure;
var t = i.HasValue;

We don't know if f is null or not.

  1. If f is null, then the result (i) is null
  2. If f is not null, then the result (i) is an int

Therefore, i is defined as int?, and t is a bool

Now, let's walk through this:

var f = ???;
var i = f?.Measure.HasValue;
  1. If f is null, then the result (i) is null
  2. If f is not null, then the result (i) is Measure.HasValue, which is a bool.

Therefore, i is a bool?.

If f is null, we short-circuit and return null. If it's not, we return the bool result of .HasValue.

Essentially, when using ?. - the return type must be a reference value, or a Nullable<T>, as the expression can short circuit to return null.