why do we prefer ? to ?? operator in c#?
我最近发现我们可以用??操作员检查空值。请检查以下代码示例:
1 |
这和
1 |
我检查了整个项目源代码库和其他一些开放源代码项目。这个
我只是想知道这背后有什么原因,比如性能问题或者其他什么?
编辑:
我刚刚根据递归安东的注释更新了我的示例代码。粗心是个错误。:(
当检查空值时,空合并运算符更清晰,这是它的主要用途。它也可以用链子锁起来。
1 2 3 4 |
虽然该运算符仅限于空检查,但三元运算符不是。例如
1 2 | bool isQuestion = true; string question = isQuestion ?"Yes" :"No"; |
我认为人们只是不知道空合并运算符,所以他们使用三元运算符。在大多数C语言中,三元存在于C之前,因此如果您不知道C的内部和外部以及/或者您用另一种语言编程,三元是一种自然的选择。但是,如果要检查空值,请使用空合并运算符,它是为此而设计的,并且IL是稍微优化的(比较??到if-then-else)。
下面是一个比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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
仅空合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | .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 |
仅三进制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | .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 |
如果那样的话,只有其他的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | .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操作符都需要相同的执行时间。
这个??运算符(也称为空合并运算符)不如三元运算符知名,因为它首次使用.NET 2.0和可空类型。不使用它的原因可能包括不了解它的存在,或者更熟悉三元运算符。
这就是说,检查空值并不是三元运算符的唯一优点,因此它不是它的替代品,更像是满足特定需求的更好的替代品。:)
我能想到的一个原因是这个运算符是在.NET 2.0中引入的,因此.NET 1.1的代码不能包含它。
我同意你的看法,我们应该经常使用这个。
参考链接
根据鲍勃的回答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | .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 } |
一个原因(其他人已经接触过)可能是缺乏意识。它也可能是(在我自己的例子中),希望尽可能减少在代码库中执行类似操作的方法的数量。所以我倾向于对所有紧凑的if-a-condition-is-met-do-this-others-do-that情况使用三元运算符。
例如,我发现以下两个陈述在概念层面上非常相似:
1 2 | return a == null ? string.Empty : a; return a > 0 ? a : 0; |
我认为这只是其他语言的习惯。是吗??操作员不使用任何其他语言。
我本以为相当于
1 | var res = data ?? data.toString(); |
将是
1 | var res = (data!=null) ? data : data.toString(); |