哪个更快:Null合并运算符、三元运算符还是if语句?

8

我们使用?? 运算符来处理 null 值表达式,例如:

string foo = null;
string bar = "woooo";
string foobar= foo ?? bar ; 
// Evaluates foobar as woooo

我们还使用了一个 if 语句,在与上述表达式一起使用时具有相同的功能。
string foo = null;
string bar = "woooo";
if(foo==null)
   string foobar=   "woooo" ;
// Evaluates foobar as woooo same as above

还有?: 三元运算符...

string foo = null;
string bar = "woooo";    
string foobar= foo==null ? "woooo" : null ;
// Evaluates foobar as woooo same as above

我知道 null 合并运算符在语法上是精确的,但是在两者中哪一个编译更快、执行更快,为什么呢?

1
如果你非常关注运行时间,C# 可能不是你最好的选择。 - Nick Vaccaro
2
如果这对你的代码造成了性能问题,那么你有更大的问题需要关注。 - Servy
1
第二个代码示例与第一个不同!如果 foo 不是 null,第一个示例将把 foo 分配给 foobar。而你的第二个示例则没有这样做。 - comecme
1
@comecme 更糟糕的是:嵌入语句不能是声明或标记语句 - asawyer
2
很奇怪这个问题被关闭为“不是一个真正的问题”,在我看来这是一个好问题。我认为“性能优先”通常是错误的策略,但在我的书中它仍然是一个有效的问题。 - James Michael Hare
显示剩余5条评论
2个回答

27
你可能正在问错误的问题。你选择使用一个而不是另一个主要不是因为效率(尽管它可能是次要的考虑因素),而是因为实用性。
实际上,你应该将 ?? 与 ?: 进行比较,而不是将其与 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);

这两者在很多方面都相似(都会产生一个值),但并不适合用于流程控制。

因此,让我们看看它们的区别。我们以三种方式编写此代码:

// Way 1 with if
string foo = null;
string folder = foo;

if (folder == null)
{
    folder = "bar";
}

// Way 2 with ? :
string foo2 = null;
var folder2 = foo2 != null ? foo2 : "bar";

// Way 3 with ??
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.

我必须像上面的例子中提到的那样将 foobar 分配给某个值。因此,在我的情况下,我使用了 if。使用三元运算符并不合适。 - Mayank Pathak
方式1和方式3是相同的(C#代码) - comecme
抱歉,剪切和粘贴错误,已经更正了... - James Michael Hare
@JamesMichaelHare非常详细的回答,谢谢,真的很有帮助.. :) +1并接受它作为答案.. - Mayank Pathak
我也会更新我的问题,因为根据你的答案,这将更有帮助.. :) - Mayank Pathak
感谢您提供详细的答案。在C# 8.0中,我们有更具体的方法来处理基于null条件的赋值,即“??=”。 - samdinesh

6

它们应该编译成相同的IL代码,因此执行效果也是相同的。

即使它们不同,性能差异也很小。除非我分析了我的应用程序并发现了这些语句花费了显著的时间,否则我甚至不会考虑这个问题。

除非你可以证明使用更清晰的语法会导致性能问题,否则一定要始终使用最清晰的语法。


1
我同意使用最清晰的语法,但是 ?? 运算符实际上编译出的 IL 更少。 - James Michael Hare
2
@JamesMichaelHare - 真的吗?这很令人惊讶。不幸的是,我面前没有可以查看的东西...有什么区别吗? - Justin Niessner
@JustinNiessner http://pastebin.com/DqXqk3Nm(禁用优化后) - asawyer
@asawyer - 你能否也发布一个带参数的方法的IL代码,而不是静态定义所有内容的IL代码?看起来编译器可能已经优化了那个IL代码。 - Justin Niessner
@JustinNiessner 这个看起来怎么样:http://pastebin.com/F1wHfQNM - asawyer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接