Why does the null-conditional operator change regular property access?

350 Views Asked by At

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?

2

There are 2 best solutions below

0
On

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)
0
On

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; }
}