32

null coalescing operator is right associative, which means an expression of the form

first ?? second ??third

is evaluated as

first ?? (second ?? third)

Based on the above rule, I think the following translation is not correct.

From:

Address contact = user.ContactAddress;
if (contact == null)
{
    contact = order.ShippingAddress;
    if (contact == null)
    {
        contact = user.BillingAddress;
    }
}

To:

Address contact = user.ContactAddress ??
                  order.ShippingAddress ??
                  user.BillingAddress;

Instead, I think the following is right one (Please correct me if I am wrong)

Address contact = (user.ContactAddress ?? order.ShippingAddress) ??
                   user.BillingAddress;
q0987
  • 34,938
  • 69
  • 242
  • 387
  • Given the right associativity nature of null coalescing operator I believe your code can never be successfully translated to use null coalescing operator without using a pair of opening and closing pair of parenthesis `()` to prioritize expression evaluation. – RBT Oct 17 '16 at 02:10

4 Answers4

38

The spec is actually self-contradictory on this one.

Section 7.13 of the C# 4 spec states:

The null coalescing operator is right-associative, meaning that operations are grouped from right to left. For example, an expression of the form a ?? b ?? c is evaluated as a ?? (b ?? c).

On the other hand, as has been pointed out, 7.3.1 claims that:

Except for the assignment operators, all binary operators are left-associative

I entirely agree that for simple cases it doesn't matter how you do the grouping... but there may be cases where it really matters due to implicit type conversions doing interesting things if the operands have different types.

I'll consider it further, ping Mads and Eric, and add an erratum for the relevant section of C# in Depth (which inspired this question).

EDIT: Okay, I've now got an example where it does matter... and the null coalescing operator is definitely right-associative, at least in the MS C# 4 compiler. Code:

using System;

public struct Foo
{
    public static implicit operator Bar(Foo input)
    {
        Console.WriteLine("Foo to Bar");
        return new Bar();
    }

    public static implicit operator Baz(Foo input)
    {
        Console.WriteLine("Foo to Baz");
        return new Baz();
    }
}

public struct Bar
{
    public static implicit operator Baz(Bar input)
    {
        Console.WriteLine("Bar to Baz");
        return new Baz();
    }
}

public struct Baz
{
}


class Test
{
    static void Main()
    {
        Foo? x = new Foo();
        Bar? y = new Bar();
        Baz? z = new Baz();

        Console.WriteLine("Unbracketed:");
        Baz? a = x ?? y ?? z;
        Console.WriteLine("Grouped to the left:");
        Baz? b = (x ?? y) ?? z;
        Console.WriteLine("Grouped to the right:");
        Baz? c = x ?? (y ?? z);
    }
}

Output:

Unbracketed:
Foo to Baz
Grouped to the left:
Foo to Bar
Foo to Bar
Bar to Baz
Grouped to the right:
Foo to Baz

In other words,

x ?? y ?? z

behaves the same as

x ?? (y ?? z)

but not the same as

(x ?? y) ?? z

I'm not currently sure why there are two conversions from Foo to Bar when using (x ?? y) ?? z - I need to check that out more carefully...

EDIT: I now have another question to cover the double conversion...

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • +1, that is certainly an interesting corner case. I'll update my answer to be less "authoritative" in statements involving "identical". – user7116 Jun 06 '11 at 18:54
  • @sixlettervariables: It's getting even more interesting, actually. There are some *really* weird things going on around custom conversions. I'll be posting a question later on... – Jon Skeet Jun 06 '11 at 18:58
  • check out the IL generation for nullable structs in your scenario. Odd reuse of `x` leads to the multiple "Foo to Bar". – user7116 Jun 06 '11 at 19:11
  • I agree. I can't figure out why it thinks that is useful. – user7116 Jun 06 '11 at 19:15
  • 3
    Okay, there's a somewhat simpler test without user-defined conversions. Suppose you have a class `Animal` with derived classes `Cat` and `Dog`. Then suppose you have three variables `animal`, `cat` and `dog`, each with the obvious compile-time type. Then with the expression `(animal ?? cat) ?? dog` one will find a good common base type, while `animal ?? (cat ?? dog)` will not compile because cats are not dogs and dogs are not cats. Therefore, to test the associativity of `??`, you just have to see if **`animal ?? cat ?? dog`** (with no parentheses) compiles or not. – Jeppe Stig Nielsen Sep 17 '12 at 19:01
24

Jon's answer is correct.

Just to be clear: the ?? operator in C# is right associative. I have just gone through the binary operator parser and verified that the parser treats ?? as right-associative.

As Jon points out, the spec says both that the ?? operator is right-associative, and that all binary operators except assignment are left-associative. Since the spec contradicts itself, clearly only one of these can be right. I will have the spec amended to say something like:

Except for the simple assignment, compound assignment and null coalescing operators, all binary operators are left-associative

UPDATE: As noted in the comments, the lambda operator => is also right-associative.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Just read [another question](http://stackoverflow.com/questions/12256076/) and realized that in a sense, the `=>` operator is right-associative too. This is because it's legal to write `Func> f = x => y => 0;` so there are "implicit" parantheses to the right. But of course this is not "true" right-associativity because parentheses to the left would never be meaningful. So I'm not sure if you consider this an additional "exception" that will have to be added to the specification? – Jeppe Stig Nielsen Sep 17 '12 at 19:38
  • (addition) But also with `x = y = 0` it is never meaningful to put the parentheses to the left, so there's really no difference. If the assignment operator `=` is to be called right-associative, then so is the lambda operator `=>`, isn't it? – Jeppe Stig Nielsen Sep 17 '12 at 19:42
  • @ntohl: The ternary operator is not a binary operator. – Eric Lippert Feb 18 '16 at 15:12
  • @Jeppe: the "lambda operator `=>`" is indeed right-associative, but I don't think it's an operator. – Tom May 12 '19 at 14:10
  • @Tom: Your claim is that the lambda operator is not an operator? I think you will find that the lambda operator is indeed an operator, hence the name "the lambda operator". – Eric Lippert May 12 '19 at 15:46
  • @Tom: More seriously: it's true that we often do not think of the lambda operator as an operator; obviously, I forgot to mention it when this answer was written. It's easy to think of it as not an operator because its operands are not necessarily expressions and because it doesn't take two values and produce a third. But from the perspective of the parser, it is just another operator; when we see `y = x => x + 1;` the parser needs to be able to work out that this parenthesizes as `y = (x => (x + 1));` and not something nonsensical like `y = (x => x) + 1;` or `(y = x) => (x + 1);` – Eric Lippert May 12 '19 at 16:12
  • @Tom: Similarly, we usually don't think of `.` as a binary operator, though it is, and we don't usually think of function calling as an n-ary operator, but it is. – Eric Lippert May 12 '19 at 16:37
  • Thanks for your comments, @Eric! I guess it all depends on the exact definitions one uses, but I think for most use cases it makes sense to define an operator as something which takes expressions as operands and results in an expression which is evaluated by applying some function to the evaluated values of the operands. Sometimes this definition is stretched to allow lazy/short-circuit evaluation. But it does not cover assignments, `=>` or `.`. It does indeed cover function calling, though. Now, all that applies to the semantic level of the program, which is what I was thinking about. – Tom May 12 '19 at 21:21
  • @Eric: But at the parser's level, I guess it indeed makes sense that all those things are operators: not only does the parser treat them all very similarly to one another, but indeed for the parser all the operands involved are expressions (even those that don't represent something we would call an expression in the program), and it applies something to them to generate a resulting expression. Does that make sense? – Tom May 12 '19 at 21:21
  • @Tom: I take your point, but I note that (1) `x = y` has expressions on both sides and produces a value as its result, (2) `x => y` has expressions on both sides and produces a value as its result, so if you're saying that you think of operators as "things that have expressions on both sides and produce a value as a result", surely assignment and lambda operators are in these cases meeting your definition. – Eric Lippert May 13 '19 at 17:09
  • @Eric: The thing is, `x` to the left of `=` or `=>` - and in the case of the latter also to the right - is not an expression in the same way as it is to the left or to the right of `+`; for example, the way the evaluation of the resulting expression relates to the value of `x` is very different. Also, to the left of `=>` we can find other things which are otherwise not valid expressions (at least before C# 7), such as `()` and `(x, y)`. – Tom May 14 '19 at 21:26
  • @Eric: I think we can agree that there are some fundamental differences between these "operators" and other, "normal" operators. And we can also agree that for some purposes it does however indeed make sense to classify all of them as operators. – Tom May 14 '19 at 21:27
5

I can't see how it matters, both:

(a ?? b) ?? c

and

a ?? (b ?? c)

have the same result!

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    They do when the types of `a`, `b` and `c` are the same. But suppose the types are A, B and C, and there are implicit conversions from A to B, A to C and B to C... then what happens? See my answer :) – Jon Skeet Jun 06 '11 at 18:48
  • @Jon: I did think of that, then looked at the question again and decided it's irrelevant. But I do applaud you for digging in and determining the actual observable associativity. – Ben Voigt Jun 06 '11 at 19:20
4

Both work as expected and are effectively identical because the expressions involve simple types (thank you @Jon Skeet). It will chain to the first non-null from left to right in your examples.

The precedence (thanks @Ben Voigt) is more interesting when combining this operator with operators of different precedence:

value = A ?? B ? C : D ?? E;

Basically, associativity is expressed inherently through operator precedence or through user introduced sub-expressions (parentheses).

user7116
  • 63,008
  • 17
  • 141
  • 172
  • I still don't think the associativity matters, only the precedence. The ternary operator has higher precedence, hence it's: `A ?? (B ? C : D) ?? E`. And it still doesn't matter whether it's `(A ?? (B ? C : D) )?? E` or `A ?? ((B ? C : D) ?? E)` – Ben Voigt Jun 04 '11 at 17:08
  • 2
    OMFG, PLEASE do not EVER write this in any code that I will ever have to read!!! Yes, I realize that you are only illustrating your point. – DOK Jun 04 '11 at 17:08
  • +1 for that horribly ambiguous piece of code, which I can only hope will never see the light of production. – Frédéric Hamidi Jun 04 '11 at 17:09
  • @Ben: I think I'm with you on this one. You never care about "associativity" when writing parser rules, just precedence. Good catch. – user7116 Jun 04 '11 at 17:09
  • @Ben: reading the list at MSDN it looks like `??` has higher precedence than `?:`. – user7116 Jun 04 '11 at 17:13
  • 1
    @sixletter: Oh drat: I just looked that up, and managed to forget in between browser tabs :(. Then it's `(A ?? B)? C : (D ?? E)` and there are no adjacent operations of equal precedence, so the associativity rule isn't even applied. – Ben Voigt Jun 04 '11 at 17:26
  • @Ben: its ok, I think we're all in agreement that the OP's question is effectively identical due to the context AND that I may get taken out back for suggesting such horrific code to illustrate precedence. – user7116 Jun 04 '11 at 17:27
  • 2
    @sixletter: There's no other way to illustrate precedence than confusing code, and there's no other use for confusing code than illustration of precedence (and motivation for adding parentheses even when not strictly necessary). – Ben Voigt Jun 04 '11 at 17:37
  • 1
    @sixlettervariables: They're not effectively identical when the types are different and conversions get involved. See my answer. – Jon Skeet Jun 06 '11 at 18:49