你可能正在问错误的问题。你选择使用一个而不是另一个主要不是因为效率(尽管它可能是次要的考虑因素),而是因为实用性。
实际上,你应该将 ?? 与 ?: 进行比较,而不是将其与 if 进行比较,因为它们有不同的目的。是的,它们都是某种形式的“条件”好处,但关键是,?? 和 ?: 都会评估一个值,而 if 不会,因此它们通常具有不同的用途。
例如,以下代码:
Console.WriteLine("The order} is for {1} product",
orderId, productId ?? "every");
使用 if
将更加笨重:
if (productId == null)
{
Console.WriteLine("The order {0} is for every product",
orderId);
}
else
{
Console.WriteLine("The order {0} is for {1} product",
orderId, productId);
}
是的,你可以将其压缩为一个,但那样你会有一个临时变量等等:
if (productId == null)
{
productId = "every";
}
Console.WriteLine("The order {0} is for {1} product",
orderId, productId);
因此,你不应该将两者进行比较,因为
??
的目的是在参数为
null
时求值得到一个值,而
if
的目的是执行不同的路径(不能直接产生一个值)。
所以,一个更好的问题可能是,为什么不用这个呢:
Console.WriteLine("The order {0} is for {1} product",
orderId, productId == null ? "every" : productId);
这两者在很多方面都相似(都会产生一个值),但并不适合用于流程控制。
因此,让我们看看它们的区别。我们以三种方式编写此代码:
string foo = null;
string folder = foo;
if (folder == null)
{
folder = "bar";
}
string foo2 = null;
var folder2 = foo2 != null ? foo2 : "bar";
string foo3 = null;
var folder3 = foo3 ?? "bar";
对于IF,我们得到以下的IL:
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldnull
IL_0005: ceq
IL_0007: ldc.i4.0
IL_0008: ceq
IL_000A: stloc.1
IL_000B: ldloc.1
IL_000C: brtrue.s IL_0016
IL_000E: nop
IL_000F: ldstr "bar"
IL_0014: stloc.0
对于条件运算符(? :),我们得到以下IL:
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: brtrue.s IL_000D
IL_0006: ldstr "bar"
IL_000B: br.s IL_000E
IL_000D: ldloc.0
IL_000E: nop
IL_000F: stloc.1
对于空合并运算符(??)的IL代码如下:
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: dup
IL_0005: brtrue.s IL_000D
IL_0007: pop
IL_0008: ldstr "bar"
IL_000D: stloc.1
注意每个操作符都越来越简单了吗?因为if
需要分支逻辑来处理不同的语句,所以它更大。而?:
更小,因为它仅仅计算一个值(不需要分支到其他语句),但仍然需要加载操作数与null
进行比较。
??
是所有操作符中最简单的,因为有一条IL指令用于与null
进行比较(而非加载null
并将其与之进行比较)。
因此,总体来说,您要考虑的是这些操作符在IL方面的差异非常微小,可能对性能产生或不产生影响。无论如何,相对于程序中更加繁重的工作(数学、数据库、网络等),这都会产生非常少的主要差异。
因此,我建议选择最易读的那个,并且只有在通过分析发现当前方法不足并成为瓶颈时才进行优化。
对我来说,使用?:
或??
的真正原因是当你想要最终结果是一个值时。也就是说,每当你想编写以下内容时:
if (someCondition)
x = value1;
else
x = value2;
然后我会使用条件语句 (
?:
) ,因为它是一个很好的速记法。根据这个条件,
x
得到其中一个值...
然后我会进一步使用
??
并说同样的道理,你想基于标识符的 null 值性质来为变量分配一个值。
所以
if
对于流程控制非常好,但如果你只返回两个值中的一个或者基于条件分配其中一个值,我会适当地使用
?:
或
??
。
最后要记住的是,这些东西在底层的实现方式(IL 和相关性能)随着 .NET Framework 的每个版本而改变(在我写下这篇文章时它们都非常接近可以忽略不计)。
因此,今天可能更快的东西明天可能就不再是最快的了。所以,我还是建议选择最适合你且易于阅读的那一个。
更新:
顺便说一句,对于真正痴迷于性能的人,我比较了上面每个代码片段的 10,000,000 次迭代,以下是执行每个代码所需的总时间。看起来对我来说
??
最快,但这些差距非常小,几乎可以忽略不计...
10,000,000 iterations of:
?: took: 489 ms, 4.89E-06 ms/item.
?? took: 458 ms, 4.58E-06 ms/item.
if took: 641 ms, 6.41E-06 ms/item.
foo
不是null
,第一个示例将把foo
分配给foobar
。而你的第二个示例则没有这样做。 - comecme嵌入语句不能是声明或标记语句
。 - asawyer