web-dev-qa-db-ja.com

なぜ私たちは好むのですか?へ?? C#の演算子?

最近使用できることがわかりました??ヌルをチェックする演算子。以下のコードサンプルを確認してください。

   var res = data ?? new data();

これはまさに

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

プロジェクトソースリポジトリ全体と他のいくつかのオープンソースプロジェクトをチェックしました。この ??演算子は使用されていません。

これの背後には、パフォーマンスの問題などの理由があるのでしょうか。

編集:

再帰的な&アントンからのコメントに基づいてサンプルコードを更新しました。不注意なのは間違い。 :(

39
RameshVel

Nullの合体演算子は、nullをチェックするときにより明確になり、それがその主な目的です。連鎖させることもできます。

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

その演算子はnullチェックに制限されていますが、三項演算子はそうではありません。例えば

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

私は人々がヌル合体演算子を知らないので、代わりに三項演算子を使用していると思います。 Ternaryは、ほとんどのCスタイル言語でC#より前に存在したため、C#の詳細がわからない場合や別の言語でプログラムされている場合は、3項が自然な選択肢です。ただし、nullをチェックする場合は、null合体演算子を使用します。これは、そのために設計されており、ILはわずかに最適化されています(??をif 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;

最初に、null合体の構文を見てください。それははるかに明確です。 Ternaryは本当に混乱しています。 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 

それ以外の場合のみ

.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は私の強みの1つではないので、誰かが私の回答を編集して拡張することができます。私は自分の理論を説明するつもりでしたが、私は自分自身と他の人を混乱させたくありません。 LOCの数は3つすべてで同じですが、すべてのILオペレーターが同じ時間で実行されるわけではありません。

58
Bob

??演算子( null-coalescing演算子 とも呼ばれます)は、.NET 2.0およびNullable型でデビューしたため、3項演算子ほど知られていません。これを使用しない理由としては、おそらく存在しないことに気付かない、または三項演算子に精通していることが挙げられます。

とは言っても、nullのチェックは三項演算子が有効な唯一のものではないため、それ自体はその代わりではなく、非常に具体的なニーズに対するより良い代替手段のようです。 :)

12
Rytmis

私が考えることができる1つの理由は、この演算子が.NET 2.0で導入されたため、.NET 1.1のコードではそれができないためです。

私はあなたに同意します、これをもっと頻繁に使うべきです。

ref リンク

6
Binoj Antony

ボブの 回答に基づく

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 
}
4
Matthew Whited

(他の人がすでに触れたように)1つの理由は、意識の欠如である可能性があります。また、(私自身の場合のように)コードベースで同様のことを行うためのアプローチの数をできるだけ少なくしたいという希望もあり得ます。したがって、私はすべてのコンパクトなif-a-condition-is-met-do-this-otherwise-do-thatの状況で三項演算子を使用する傾向があります。

たとえば、次の2つのステートメントは概念レベルではかなり似ています。

return a == null ? string.Empty : a;    
return a > 0 ? a : 0;
2
Fredrik Mörk

それは他の言語の習慣だと思います。私の知る限り、 ??演算子は他の言語では使用されません。

1
Karel Bílek

私は同等のことを考えていただろう

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

だろう

var res = (data!=null) ? data : data.toString();
0
Bryan