空合并赋值运算符的行为

The behavior of null-coalescing assignment operator

我只是想知道 ??= 运算符如何在后台运行。我有两个问题。

考虑以下示例,

string name = "John";
name ??= "George";

1)是否等于name = name ?? "George";

2) 是不是这样,

if (name == null) {
   name = "George";
}

if (name == null) {
   name = "George";
}
else {
   name = name;
}

会被评价为:

string text = "John";
if (text == null)
{
    text = "George";
}

您可以使用 sharplab 查看实际发生的情况:

https://sharplab.io/#v2:CYLg1APgAgTAjAWAFBQMwAJboMLoN7LpGYZQAs6AsgBQCU+hxTUcADOgHYCGAtgKboAvOgBEAKQD2ACw4iA3Mm790AfhXCRAcT4SATgHM+8xkQC+yU0A

更多信息:

基于documentation

C# 8.0 introduces the null-coalescing assignment operator ??=. You can use the ??= operator to assign the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null.

根据documentation

null-coalescing assignment operator ??= assigns the value of its right-hand operand to its left-hand operand only if the left-hand operand evaluates to null. The ??= operator doesn't evaluate its right-hand operand if the left-hand operand evaluates to non-null.

在您的代码示例中,它不会被评估,因为 name 不是 null

string name = "John";
name ??= "George";

如果你这样写就可以了

string name = null;
name ??= "George";

名称值将为 George。扩展变体是

if (name is null) //or name == null
{
    name = "George";
}

The null-coalescing operator ?? returns the value of left-hand operand if it isn't null; otherwise, it evaluates the right-hand operand and returns result.

在此示例中 name = name ?? "George" 只有当名称之前有 null 值时,结果才会是 George。我的样本 name = name ?? "George"; 就 return 结果而言等于 name ??= "George";。但是在这两种情况下,只有当原始 namenull 时,您才能获得 George 值。也可以参考language specification了解详情

您可以使用 https://sharplab.io/ 来测试差异。 ??=?? 之间的区别非常小,实际上 一旦代码被 JIT 编译就消失了

简而言之:

  1. 一旦代码被编译成汇编,就一样了。
  2. 等价于:
if (text == null){
    text = "George";
}

SharpLab 示例

this example的代码:

public void M1() {
    string name = "John";
     name ??= "George";
    Console.WriteLine(name);
}

public void M2() {
    string name = "John";
    name = name  ?? "George";
    Console.WriteLine(name);
}

生成这个中间 C# 代码,显示出真正的区别:

public void M1()
{
    string text = "John";
    if (text == null)
    {
        text = "George";
    }
    Console.WriteLine(text);
}

public void M2()
{
    string text = "John";
    text = (text ?? "George");
    Console.WriteLine(text);
}

虽然 IL 是 almost the same,但 dup(复制)和 pop 操作除外。你会认为 ?? 为此有点慢:

    IL_0000: nop
    IL_0001: ldstr "John"
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: brtrue.s IL_0010
    IL_000a: ldstr "George"
    IL_000f: stloc.0
    IL_0010: ldloc.0
    IL_0011: call void [System.Console]System.Console::WriteLine(string)
    IL_0016: nop
    IL_0017: ret

对比

        IL_0000: nop
        IL_0001: ldstr "John"
        IL_0006: stloc.0
        IL_0007: ldloc.0
***     IL_0008: dup
        IL_0009: brtrue.s IL_0011
***     IL_000b: pop
        IL_000c: ldstr "George"
        IL_0011: stloc.0
        IL_0012: ldloc.0
        IL_0013: call void [System.Console]System.Console::WriteLine(string)
        IL_0018: nop
        IL_0019: ret

但是 the assembly in Release mode 是相同的:

C.M1()
    L0000: mov ecx, [0x1a58b46c]
    L0006: test ecx, ecx
    L0008: jnz L0010
    L000a: mov ecx, [0x1a58b470]
    L0010: call System.Console.WriteLine(System.String)
    L0015: ret

C.M2()
    L0000: mov ecx, [0x1a58b46c]
    L0006: test ecx, ecx
    L0008: jnz L0010
    L000a: mov ecx, [0x1a58b470]
    L0010: call System.Console.WriteLine(System.String)
    L0015: ret