41

私は最近、使用できることを発見しました?? null をチェックする演算子。以下のコード サンプルを確認してください。

   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.

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

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

人々は null 合体演算子を認識していないため、代わりに三項演算子を使用していると思います。ほとんどの C スタイル言語では、C# よりも前に 3 項が存在していたため、C# の内外を知らない場合や、別の言語でプログラミングしたことがある場合は、3 項を選択するのが自然です。ただし、null をチェックしている場合は、null 合体演算子を使用します。これはそのために設計されており、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;

まず、null 合体の構文を見てください。より明確です。三項は本当に紛らわしいです。次に、IL を見てみましょう

Null 合体のみ

.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 

3 進数のみ

.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 のみ

.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 演算子の実行に同じ時間がかかるわけではありません。

于 2009-10-09T12:45:14.423 に答える
12

?? 演算子 ( null 合体演算子とも呼ばれます) は、.NET 2.0 と Nullable 型でデビューしたため、三項演算子ほど知られていません。それを使用しない理由には、おそらくそれが存在することを認識していないこと、または三項演算子に慣れていることが含まれます。

とはいえ、null のチェックだけが三項演算子に適しているわけではありません。そのため、三項演算子の代わりになるものではなく、非常に具体的なニーズに対するより優れた代替手段のようです。:)

于 2009-10-09T12:45:28.870 に答える
6

考えられる理由の 1 つは、この演算子が .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

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

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

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

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

于 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 に答える