【问题标题】:The behavior of null-coalescing assignment operator空合并赋值运算符的行为
【发布时间】:2020-02-11 08:22:09
【问题描述】:

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

考虑以下示例,

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

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

2) 是这样的吗,

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

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

【问题讨论】:

  • if (name != null) – 你的意思是== 吗?这两种情况基本相同,因为else 分支没有任何意义(并且可能会被编译器丢弃)
  • 与 2) 中的第一个版本相同。如果您自己编写第二个版本,您会看到 else 分支中的代码将被编译器优化掉。
  • Furkan, if (name != null) { 应该改为 if (name == null) {
  • 您需要参考语言设计提案,其中包括降级形式:github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/…

标签: c# .net-core c#-8.0


【解决方案1】:

它将被评估为:

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

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

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

更多信息:https://stackoverflow.com/a/59300172/2946329

基于documentation

C# 8.0 引入了空值合并赋值运算符 ??=。你可以 使用 ??= 运算符将其右侧操作数的值分配给 仅当左侧操作数的计算结果为 null 时,它的左侧操作数。

【讨论】:

  • 我的第一个问题呢?
  • @FurkanÖztürk:请不要在一个问题中问两个问题。正如您刚刚发现的那样,通常发生的情况是只有其中一个得到回答。如果您有两个问题,请发布两个不同的问题。
【解决方案2】:

根据documentation

null-coalescing 赋值运算符 ??= 分配其值 仅当左手操作数为右手操作数时 操作数的计算结果为 null??= 运算符不评估其 如果左侧操作数的计算结果为非空,则为右侧操作数。

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

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

如果你写这样的东西,它会起作用

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

名称值为George。扩展变体是

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

空合并运算符??返回左手的值 如果不是null,则操作数;否则,它评估右手 操作数并返回结果。

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

【讨论】:

  • 我的第一个问题呢?
  • @FurkanÖztürk 我正在更新答案
【解决方案3】:

您可以使用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

【讨论】:

    猜你喜欢
    • 2010-10-13
    • 2021-08-29
    • 2011-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-13
    相关资源
    最近更新 更多