Search code examples
c#null-coalescing-operator

Null-coalescing and right-associative in C# - clarification?


I've seen a tweet about the null-coalescing operator (which is right associative ):

From the SPEC:

For example, an expression of the form a ?? b ?? c is evaluated as a ?? (b ?? c)

So there was another guy which replied that it can be tested and verified with an example :

void Main()
{
    Console.WriteLine ((P)null ?? (string)null ?? "b555");
}

public class P
{
 public static implicit operator P(string b) {throw new Exception(b??"a");}
}

Result :

Exception: b555 

But I didn't understand the behaviour.

Question

I already know that the ?? has a very low precedence but still :

(P)null should evaluate first (higher precedence) !

But it seems that

a ?? (b ?? c)

Is evaluated first.

Why ?

In other words , it seems that these are the events:

 (P)(null ?? ((string)null ?? "b555"))

And then :

(P)(null ?? "b555")

And then :

(P)"b555"

But I don't understand why (P) is applied on all the coalesce expression and not on the null ( in (P)null)


Solution

  • Why should your implicit conversion be applied to null in (P)null? (P)null yields a null-reference statically typed to P, why should any conversion from string be applied here? There is no mention of any string in (P)null.

    Note how the compiler statically types the following expressions¹:

    ((string)null ?? "b555")   -> string
    
    ((P)null ?? ...some expression of type string...)    -> P
    

    Thus,

    ((P)null ?? (string)null ?? "b555")     -> P
    

    Now the expression can be resolved as follows:

    1. (P)null is null, so we look at the right-hand side.

    2. ((string)null ?? "b555") yields the string "b555" (no P involved).

    3. ((P)null ?? "b555") results in a value of "b555". Since the static type of ((P)null ?? "b555") is P, b555 is implicitly converted to P, triggering your implicit conversion.

    4. We get an Exception "b555", as expected.

    PS: If anyone is interested in a more detailed explanation in the form of a dialog, we have taken this topic to chat and here is the transcript.


    ¹ Proof:

    public static void Main()
    {
        var x = ((P)null ?? "abc");
        x.A();   // compiles
    }
    
    public class P
    {
        public static implicit operator P(string b) {throw new Exception(b??"a");}
        public void A() {}
    }