C# 中的空合并和右关联 - 说明?

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

我看过一条关于 null-coalescing 运算符(右结合)的推文:

来自规范:

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

于是又有人回复说可以用例子来测试和验证:

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

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

结果:

Exception: b555 

但我不理解这种行为。

问题

我已经知道 ?? 的优先级 非常低 但仍然 :

(P)null 应该首先评估 (更高的优先级)!

不过好像

a ?? (b ?? c)

先评价。

为什么?

换句话说,似乎是这些事件:

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

然后:

(P)(null ?? "b555")

然后:

(P)"b555"

但我不明白为什么 (P) 应用于所有合并表达式而不是 null(在 (P)null

(P) 仅应用于第一个 null。但是,class P 具有从字符串到 P 的隐式转换,因此它会抛出异常。由于 implicit 关键字,它不需要您明确请求类型转换。

为什么要将隐式转换应用于 (P)null 中的 null(P)null 产生静态类型为 P 的空引用,为什么要在此处应用来自字符串 的任何 转换? (P)null.

中没有提到任何字符串

注意编译器如何静态键入以下表达式¹:

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

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

因此,

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

现在表达式可以解析如下:

  1. (P)null为空,所以我们看右边。

  2. ((string)null ?? "b555") 产生字符串 "b555"(不涉及 P)。

  3. ((P)null ?? "b555") 结果为 "b555"。由于((P)null ?? "b555")的静态类型是Pb555被隐式转换为P,触发你的隐式转换。

  4. 如预期的那样,我们得到了异常“b555”。

PS:如果大家有兴趣以对话的形式进行更详细的解释,我们已经带着这个话题来聊聊here is the transcript.


¹ 证明:

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() {}
}