关于c#:null合并运算符的右关联如何表现?


How the right associative of null coalescing operator behaves?

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

first ?? second ??third

is evaluated as

first ?? (second ?? third)

基于上述规则,我认为以下翻译不正确。

来自:

1
2
3
4
5
6
7
8
9
Address contact = user.ContactAddress;
if (contact == null)
{
    contact = order.ShippingAddress;
    if (contact == null)
    {
        contact = user.BillingAddress;
    }
}

到:

1
2
3
Address contact = user.ContactAddress ??
                  order.ShippingAddress ??
                  user.BillingAddress;

相反,我认为以下是正确的(如果我错了请纠正我)

1
2
Address contact = (user.ContactAddress ?? order.ShippingAddress) ??
                   user.BillingAddress;


这个规范实际上是自相矛盾的。

C 4规范第7.13节规定:

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).

另一方面,正如所指出的,7.3.1声称:

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

我完全同意,对于简单的情况,你如何分组并不重要…但是,在某些情况下,如果操作数具有不同的类型,则隐式类型转换会执行有趣的操作,这可能真的很重要。

我会进一步考虑,Ping Mads和Eric,并为C的相关部分添加一个勘误表(这激发了这个问题)。

编辑:好吧,我现在有一个例子,它确实很重要……空合并运算符绝对是右关联的,至少在MS C_4编译器中是这样。代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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);
    }
}

输出:

1
2
3
4
5
6
7
8
Unbracketed:
Foo to Baz
Grouped to the left:
Foo to Bar
Foo to Bar
Bar to Baz
Grouped to the right:
Foo to Baz

换言之,

1
x ?? y ?? z

行为与

1
x ?? (y ?? z)

但与

1
(x ?? y) ?? z

我不知道为什么在使用(x ?? y) ?? z时会有两个从foo到bar的转换-我需要更仔细地检查…

编辑:我现在还有一个问题要讨论双重转换…


乔恩的回答是正确的。

要清楚一点:c中的??操作符是右相关的。我刚刚检查了二进制运算符解析器,并验证了解析器是否将??视为右关联。

正如jon所指出的,规范既指出??操作符是右相关的,也指出除赋值之外的所有二进制操作符都是左相关的。由于规范本身矛盾,很明显只有一个是正确的。我将修改规范,以表示如下内容:

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

更新:正如注释中所指出的,lambda运算符=>也是右相关的。


我看不出这两者的重要性:

1
(a ?? b) ?? c

1
a ?? (b ?? c)

有同样的结果!


两者都按预期工作,并且有效地相同,因为表达式涉及简单类型(谢谢@jon skeet)。在您的示例中,它将从左到右链接到第一个非空值。

当将此运算符与具有不同优先级的运算符组合时,优先级(谢谢@ben voigt)更有趣:

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

基本上,关联性是通过运算符优先级或用户引入的子表达式(括号)内在地表示的。