41

我最近发现我们可以使用 ?? 运算符来检查空值。请检查以下代码示例:

   var res = data ?? new data();

这与

   var res = (data==null) ? new data() : data ;

我检查了我的整个项目源代码库和其他一些开源项目。而且这个??运算符从未被使用过。

我只是想知道这背后是否有任何原因,比如性能问题或其他什么?

编辑:

我刚刚根据 recursive & Anton 的评论更新了我的示例代码。这是粗心的错误。:(

4

7 回答 7

63

在检查 null 时,null 合并运算符更加清晰,这是它的主要目的。也可以串起来。

object a = null;
object b = null;
object c = new object();
object d = a ?? b ?? c; //d == c.

虽然该运算符仅限于空检查,但三元运算符不是。例如

bool isQuestion = true;
string question = isQuestion ? "Yes" : "No";

我认为人们只是不知道空合并运算符,因此他们改用三元运算符。在大多数 C 风格的语言中,三进制在 C# 之前就存在,所以如果你不了解 C# 和/或你用另一种语言编程,那么三进制是一个自然的选择。但是,如果您要检查 null,请使用 null coalesce 运算符,它是为此而设计的,并且 IL 进行了轻微优化(将 ?? 与 if then else 进行比较)。

这是一个比较每种用法的示例

object a = null;
object b = null;
object c = null;

object nullCoalesce = a ?? b ?? c;

object ternary = a != null ? a : b != null ? b : c;

object ifThenElse;

if (a != null)
    ifThenElse = a;
else if (b != null)
    ifThenElse = b;
else if (c != null)
    ifThenElse = c;

首先,只看空合并的语法,它更清晰。三元真的很混乱。现在让我们看看 IL

仅空合并

.entrypoint
.maxstack 2
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object nullCoalesce)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: dup 
L_000c: brtrue.s L_0015
L_000e: pop 
L_000f: ldloc.1 
L_0010: dup 
L_0011: brtrue.s L_0015
L_0013: pop 
L_0014: ldloc.2 
L_0015: stloc.3 
L_0016: ldloc.3 
L_0017: call void [mscorlib]System.Console::WriteLine(object)
L_001c: ret 

仅三元

.entrypoint
.maxstack 2
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object ternary)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: brtrue.s L_0016
L_000d: ldloc.1 
L_000e: brtrue.s L_0013
L_0010: ldloc.2 
L_0011: br.s L_0017
L_0013: ldloc.1 
L_0014: br.s L_0017
L_0016: ldloc.0 
L_0017: stloc.3 
L_0018: ldloc.3 
L_0019: call void [mscorlib]System.Console::WriteLine(object)
L_001e: ret 

If Then Else only

.entrypoint
.maxstack 1
.locals init (
    [0] object a,
    [1] object b,
    [2] object c,
    [3] object ifThenElse)
L_0000: ldnull 
L_0001: stloc.0 
L_0002: ldnull 
L_0003: stloc.1 
L_0004: newobj instance void [mscorlib]System.Object::.ctor()
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: brfalse.s L_0011
L_000d: ldloc.0 
L_000e: stloc.3 
L_000f: br.s L_001a
L_0011: ldloc.1 
L_0012: brfalse.s L_0018
L_0014: ldloc.1 
L_0015: stloc.3 
L_0016: br.s L_001a
L_0018: ldloc.2 
L_0019: stloc.3 
L_001a: ldloc.3 
L_001b: call void [mscorlib]System.Console::WriteLine(object)
L_0020: ret 

IL 不是我的强项之一,所以也许有人可以编辑我的答案并对其进行扩展。我打算解释我的理论,但我不想混淆自己和他人。这三个的 LOC 数量相似,但并非所有 IL 运算符都花费相同的时间来执行。

于 2009-10-09T12:45:14.423 回答
12

这 ??运算符(也称为null-coalescing 运算符)比三元运算符鲜为人知,因为它在 .NET 2.0 和 Nullable 类型中首次亮相。不使用它的原因可能包括不知道它的存在,或者更熟悉三元运算符。

也就是说,检查 null 并不是三元运算符的唯一优点,因此它不能替代它,更像是满足特定需求的更好选择。:)

于 2009-10-09T12:45:28.870 回答
6

我能想到的一个原因是这个运算符是在 .NET 2.0 中引入的,所以 .NET 1.1 的代码不能拥有它。

我同意你的看法,我们应该更频繁地使用它。

参考链接

于 2009-10-09T12:45:15.993 回答
4

基于鲍勃的回答

public object nullCoalesce(object a, object b, object c)
{
    return a ?? b ?? c;
}
public object ternary(object a, object b, object c)
{
    return a != null ? a : b != null ? b : c;
}
public object ifThenElse(object a, object b, object c)
{
    if (a != null)
        return a;
    else if (b != null)
        return b;
    else
        return c;
}

...这是发布版本中的 IL ...

.method public hidebysig instance object nullCoalesce(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: dup 
    L_0002: brtrue.s L_000b
    L_0004: pop 
    L_0005: ldarg.2 
    L_0006: dup 
    L_0007: brtrue.s L_000b
    L_0009: pop 
    L_000a: ldarg.3 
    L_000b: ret 
}

.method public hidebysig instance object ternary(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: brtrue.s L_000a
    L_0003: ldarg.2 
    L_0004: brtrue.s L_0008
    L_0006: ldarg.3 
    L_0007: ret 
    L_0008: ldarg.2 
    L_0009: ret 
    L_000a: ldarg.1 
    L_000b: ret 
}

.method public hidebysig instance object ifThenElse(
    object a, 
    object b, 
    object c) cil managed
{
    .maxstack 8
    L_0000: ldarg.1 
    L_0001: brfalse.s L_0005
    L_0003: ldarg.1 
    L_0004: ret 
    L_0005: ldarg.2 
    L_0006: brfalse.s L_000a
    L_0008: ldarg.2 
    L_0009: ret 
    L_000a: ldarg.3 
    L_000b: ret 
}
于 2009-10-09T13:45:03.367 回答
2

一个原因(正如其他人已经触及的)可能是缺乏意识。它也可能是(就像我自己的情况一样),希望尽可能减少在代码库中做类似事情的方法的数量。所以我倾向于在所有紧凑的 if-a-condition-is-met-do-this-otherwise-do-that 情况下使用三元运算符。

例如,我发现以下两个陈述在概念层面上非常相似:

return a == null ? string.Empty : a;    
return a > 0 ? a : 0;
于 2009-10-09T13:05:15.117 回答
1

我认为这只是其他语言的习惯。AFAIK, ??运算符不用于任何其他语言。

于 2009-10-09T12:46:02.823 回答
0

我会认为相当于

var res = data ?? data.toString();

将会

var res = (data!=null) ? data : data.toString();
于 2009-10-09T12:48:55.883 回答