4

I'm confused about how the null-conditional operator cascades with normal property access. Take these two examples:

a?.b.c
(a?.b).c

I would expect them to be equivalent: first, the value of a?.b is evaluated, then result.c is evaluated. Thus if a == null, an exception should be thrown.

However, that only happens in the second expression. The first expression evaluates to null, meaning it's the same as a?.b?.c. Why?

Blue
  • 22,608
  • 7
  • 62
  • 92
BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
  • 2
    This is the safe-navigation-operator. – Blue Aug 01 '18 at 18:58
  • 2
    Nope, it stops at `?.` if `a` is null. The second one ends up telling it you definitely want to evaluate `c`. – juharr Aug 01 '18 at 18:59
  • 3
    @FrankerZ In C# it's called the null conditional operator. – juharr Aug 01 '18 at 18:59
  • 2
    @FrankerZ The [official docs call it the null-conditional operator](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators), which is why I used that term. – BlueRaja - Danny Pflughoeft Aug 01 '18 at 18:59
  • I think this is a case of operator precedence. `(` and `)` ensures what's inside them gets evaluated, then what's outside. Whereas `a?.b.c` simply evaluates from left to right, and stops when `a?` becomes false. – Sach Aug 01 '18 at 19:01
  • 3
    @BlueRaja-DannyPflughoeft The important part to notice in the docs is _The null-conditional operators are short-circuiting. If one operation in a chain of conditional member access and index operation returns null, then the rest of the chain’s execution stops_ – juharr Aug 01 '18 at 19:01
  • @juharr: Ah, it's written right in the very docs I linked. Doh! Please create an answer and I'll accept it _(also, not sure why people are saying it's a matter of operator precedence; it's not, it's a matter of short-circuiting)_ – BlueRaja - Danny Pflughoeft Aug 01 '18 at 19:04
  • Another way to look at it is that the first is like `a == null ? null : a.b.c;` where as the second is like `(a == null ? null : a.b).c;` – juharr Aug 01 '18 at 19:06
  • @CamiloTerevinto It is involved in this question. In c# it's both considered the null conditional operator, but several websites consider it as a safe navigation operator as well. See [here](https://blogs.msdn.microsoft.com/jerrynixon/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator/) and [here](https://www.i-programmer.info/news/89-net/7016-c-gets-a-new-operator-safe-navigation.html). They are essentially interchangeable, and I think this question directly relates to safe navigation (As one example triggers an exception). – Blue Aug 01 '18 at 19:14
  • @juharr Please write an answer and I will accept it. I do not like either of the existing answers. – BlueRaja - Danny Pflughoeft Aug 06 '18 at 20:43

2 Answers2

9

That's only a matter of operator precedence. Let's go through the cases:

a?.b.c

  1. Evaluate a => null is returned, nothing else is evaluated given that the null-conditional operators are short-circuiting.

(a?.b).c

  1. Evaluate a => null is returned
  2. Evaluate ((B)null).c => NullReferenceException is thrown

For these cases to be equivalent, you should be comparing

  1. a?.b.c
  2. (a?.b)?.c
  3. a?.b?.c (as you already mentioned)
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
1

I don't know if this'll help or not, beyond what Camilo already provided, but here's a brief comparison showing how the logic might look without the null-conditional operator in play.

public class Program
{
    public static void Main()
    {
        A a;

        a = new A { b = new B { c = 5 } };

        Console.WriteLine(a?.b.c);        // returns 5;
        Console.WriteLine((a?.b).c);      // returns 5;

        a = null;

        Console.WriteLine(a?.b.c ?? -1);  // returns -1;
        Console.WriteLine((a?.b).c);      // throws NullReferenceException


        // Similar to a?.b.c

        if (a != null)
            Console.WriteLine(a.b.c);     // returns 5;
        else
            Console.WriteLine(-1);        // returns -1;


        // Similar to (a?.b).c

        B tmp;
        if (a != null)
            tmp = a.b;
        else
            tmp = null;

        Console.WriteLine(tmp.c);         // returns 5 or throws NullReferenceException
    }
}


public class A
{
    public B b { get; set; }
}

public class B
{
    public int c { get; set; }
}
Grant Winney
  • 65,241
  • 13
  • 115
  • 165