7

我们用?? 运算符针对空值评估表达式,例如:

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

我知道空值合并在语法上是精确的,但是两者之间哪个编译得更快并且执行得更快,为什么?

4

2 回答 2

23

你可能问错了问题。您不会选择使用其中一个而不是另一个主要是因为效率(尽管它可能是次要问题),而是由于实用性。

真的,您应该比较而不是比较,??因为它们有不同的目的。是的,它们都是某种形式的“有条件的”善,但关键是两者都评估为一个值,而不是,因此它们通常有不同的用途。?: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    

注意每个连续的如何更简单?IL 更大,if因为它需要分支逻辑来处理单独的语句。更小,?:因为它只是计算一个值(不分支到其他语句),但仍需要加载操作数以与 ( null) 进行比较。

??是最简单的,因为有一个 IL 指令用于比较null(vs 加载null和比较它。

所以所有这一切都说,你说的是 IL 方面的一个非常小的差异,这可能会或可能不会影响性能。无论如何,与程序中更密集的工作(数学、数据库、网络等)相比,这很可能没有太大的区别。

因此,我建议选择易读的一个,并且仅在通过分析发现当前方法不足且存在瓶颈时才进行优化。

对我来说,使用?:or的真正原因??是当您希望最终结果成为一个值时。也就是说,任何时候你都想写:

if (someCondition)
    x = value1;
else 
    x = value2;

然后我会使用条件 ( ?:),因为这是一个很好的速记。 x根据此条件获取一个或另一个值...

然后我会更进一步,说同样是真的,你想根据标识符的 -ness??为变量分配一个值。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.
于 2012-11-14T19:15:25.977 回答
5

它们都应该编译为相同的 IL,因此执行相同。

即使他们不这样做,性能差异也可以忽略不计。除非我分析了我的应用程序并看到这些语句花费了大量时间,否则我什至不会考虑看这个。

始终使用最清晰的语法,除非您能证明它导致了性能问题,而不是之前。

于 2012-11-14T19:03:52.757 回答