Null Coalescingオペレーターを使用するユニークな方法[終了]


164

C#でNull融合演算子を使用する標準的な方法は、デフォルト値を設定することです。

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

しかし、他に何??が使用できますか?三項演算子ほど便利ではないようですが、次の点よりも簡潔で読みやすくなっています。

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

ですから、nullの合体演算子について知っている人が少ないことを考えると...

  • ??他に使ったことはありますか?

  • ??必要な、またはあなただけの(ほとんどが精通していること)三項演算子を使用する必要があります

回答:


216

まあ、まず第一に、それは標準的な三元よりも連鎖するのがはるかに簡単です:

string anybody = parm1 ?? localDefault ?? globalDefault;

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

nullが発生する可能性のあるオブジェクトが変数でない場合にも、うまく機能します。

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
連鎖はオペレーターにとって大きなプラスであり、冗長なIFの束を削除します
chakrit

1
今日、それを使用して、3項またはnullの合体演算子について知る前に書いた簡単なIFブロックを置き換えました。元のIFステートメントのtrueブランチとfalseブランチは同じメソッドを呼び出し、特定の入力がNULLの場合、引数の1つを別の値に置き換えました。nullの合体演算子を使用すると、1つの呼び出しになります。このような置換が2つ以上必要なメソッドがある場合、これは非常に強力です。
デビッドA.グレイ

177

私はそれを遅延ロードのワンライナーとして使用しました:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

読める?自分で決める。


6
うーん、あなたは「なぜ誰かがそれを難読化されたIFとして使用したいのか」に対する反例を見つけました...それは実際に私には非常に読みやすいです。
Godeke、2008年

6
私は何かが足りないかもしれません(私はほとんどJavaを使用しています)が、そこに競合状態はありませんか?
ジャスティンK

9
@Justin K-複数のスレッドが同じオブジェクトのLazyPropプロパティにアクセスしている場合にのみ、競合状態が発生します。各インスタンスのスレッドの安全性が必要な場合は、ロックで簡単に修正できます。この例では明らかに、これは必須ではありません。
Jeffrey L Whitledge、2010年

5
@ジェフリー:はっきりしていれば、私は質問しなかっただろう。:)その例を見たとき、私はすぐにシングルトンメンバーのことを考えました。たまたま、コーディングのほとんどをマルチスレッド環境で行っているので...しかし、ええ、コードが正しいと仮定すれば、余分なものは不要です。
ジャスティンK

7
競合状態にするためにシングルトンである必要はありません。LazyPropを含むクラスの共有インスタンスと、LazyPropにアクセスする複数のスレッドだけです。Lazy <T>はこの種のことを行うためのより良い方法であり、デフォルトでスレッドセーフです(Lazy <T>のスレッドセーフを変更することを選択できます)。
Niall Connaughton 2013

52

私はそれが2つの「少し変わった」方法で役立つとわかりました:

  • ルーチンをout作成するときにパラメーターを持つ代替手段としてTryParse(つまり、解析が失敗した場合はnull値を返します)
  • 比較のための「わからない」表現として

後者はもう少し情報が必要です。通常、複数の要素を使用して比較を作成する場合、比較の最初の部分(年齢など)が明確な答えを与えるかどうかを確認し、最初の部分が役に立たなかった場合にのみ次の部分(名前など)を確認する必要があります。nullの合体演算子を使用すると、非常に単純な比較を記述できます(順序付けか等しいかに関わらず)。たとえば、MiscUtilでいくつかのヘルパークラスを使用します

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

確かに、MiscUtilにProjectionComparerといくつかの拡張機能が追加されました。これにより、この種のことがさらに簡単になりますが、それでもきちんと機能します。

Equalsの実装開始時に参照の等価性(またはnullity)をチェックする場合も同じことができます。


PartialComparerで行ったことを気に入っていますが、評価された式の変数を保持する必要があるケースを探していました。私はラムダと拡張機能に精通していないので、以下が同様のパターンに準拠しているかどうかを確認できますか(つまり、機能しますか)? stackoverflow.com/questions/1234263/#1241780
maxwellb 2008

33

別の利点は、三項演算子が二重評価または一時変数を必要とすることです。

例えばこれを考えてください:

string result = MyMethod() ?? "default value";

一方、三項演算子を使用すると、次のいずれかが残ります。

string result = (MyMethod () != null ? MyMethod () : "default value");

MyMethodを2回呼び出す、または:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

どちらの方法でも、nullの合体演算子はよりクリーンで、おそらくより効率的です。


1
+1。これが、nullの合体演算子が好きな大きな理由の1つです。呼び出しMyMethod()に何らかの副作用がある場合に特に便利です。
CVn

MyMethod()値を返す以外に効果がない場合、コンパイラーはそれを2回呼び出さないことを知っているので、ほとんどの場合、ここで効率を心配する必要はありません。
TinyTimZamboni 2013年

またMyMethod()、点線のオブジェクトのチェーンシーケンスの場合は、より見やすいIMHOが維持されます。例:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore 2013年

@TinyTimZamboni、コンパイラのこの動作のリファレンスはありますか?
Kuba Wyrostek、2016

@KubaWyrostek C#コンパイラの具体的な動作については知りませんが、llvmを使用した静的コンパイラ理論の経験はあります。このような呼び出しを最適化するためにコンパイラーが実行できるアプローチはいくつかあります。グローバル値の番号付けは、が2つの呼び出しであることMyMethodがこのコンテキストで同一であることを認識しMyMethodます。別のオプションは、自動メモ化、または単に関数をキャッシュに閉じさせることです。一方、en.wikipedia.org
wiki / Global_value_numbering

23

もう1つの考慮事項は、合体演算子は、3項のようにプロパティのgetメソッドを2回呼び出さないことです。

したがって、たとえば、3項を使用すべきでないシナリオがあります。

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

使用する場合:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

ゲッターは2回呼び出され、count変数は2に等しくなります。使用する場合:

var b = a.Prop ?? 0

count必要に応じて、変数は1になります。


4
これは、より多くの賛成投票に値します。私はすっごく何回も読んだこと??ある同等にします?:
Kuba Wyrostek、2016

1
ゲッターが2回呼び出されることについての有効なポイント。しかし、この例では、このような誤解を招くような名前のゲッターが実際にオブジェクトに変更を加えるのは、悪い設計パターンだと考えます。
Linas

15

??演算子に対して私が見つけた最大の利点は、null許容値型をnull不可値型に簡単に変換できることです。

int? test = null;
var result = test ?? 0; // result is int, not int?

私はこれをLinqクエリで頻繁に使用します。

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?

私はここで少し遅れるかもしれませんが、その2番目の例はをスローしますkvp == null。そして実際にNullable<T>は、GetValueOrDefault私が通常使用する方法があります。
CompuChip 2013年

6
KeyValuePairは.NETフレームワークの値型であるため、そのプロパティにアクセスしても、null参照例外はスローされません。msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan

9

私は使用したことがありますか?IDataErrorInfoの私の実装では:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

個々のプロパティが「エラー」状態にある場合、そのエラーが発生します。それ以外の場合は、nullになります。本当にうまくいきます。


面白い。プロパティとして「this」を使用しています。私はそれをしたことがありません。
Armstrongest

ええ、それはIDataErrorInfoの動作方法の一部です。一般に、その構文はコレクションクラスでのみ役立ちます。
Matt Hamilton、

4
あなたは、エラーメッセージを格納しているthis["Name"]this["Address"]など?
Andrew

7

オプションのパラメーターが設定されていない場合に対処するために、ヌル合体演算子を使用して少しわかりやすくすることができます。

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...

この行を次のように書くことができたら素晴らしいとarg ?= Arg.Default思いませんか?
Jesse de Wit

6

null合体演算子を使用して、特定のプロパティを遅延読み込みするのが好きです。

私の要点を説明するための非常に単純な(そして工夫された)例:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}

Resharperは実際には、これを「従来の」遅延ロードのリファクタリングとして提案します。
arichards 2017

5

?? 必要な場合、または3項演算子(ほとんどが使い慣れている)を使用する必要がある場合

実際、私の経験では、3項演算子(より正確には条件演算子)に精通している人はほとんどいません。これは、2進数または単項または2進数?:と同じ意味で「3項」ですが、多くの言語では3項演算子のみ)なので、少なくともその限られたサンプルでは、​​ステートメントはすぐに失敗します。||+

また、前述のように、null結合演算子が非常に役立つ主要な状況が1つあります。これは、評価される式に副作用がある場合です。その場合、(a)一時変数を導入するか、(b)アプリケーションの実際のロジックを変更しないと、条件演算子を使用できません。(b)明らかにどのような状況でも適切ではなく、それは個人的な好みですが、短期間の変数であっても、大量の無関係な宣言スコープを乱雑にしたくないので、(a)もそうです特定のシナリオ。

もちろん、結果に対して複数のチェックを行う必要がある場合ifは、おそらく条件演算子または一連のブロックがジョブのツールです。ただし、単純な「これがnullの場合はそれを使用し、それ以外の場合は使用する」には、nullの合体演算子??が最適です。


私からの非常に遅いコメント-しかし、三項演算子が3つの引数を持つ演算子(C#には現在複数あります)であることを誰かがカバーしているのを見て喜んでいます。
スティーブキッド

5

私が最近よく行っていることの1つは、へのバックアップにnull合体を使用することですas。例えば:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

?.それぞれが失敗する可能性のある長いチェーンをバックアップするのにも役立ちます

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";

4

唯一の問題は、null-coalesce演算子が空の文字列を検出しないことです。


すなわち

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

出力:

result1 = ""

result2 = coalesced!

私は現在上書きを検討しています?? これを回避する演算子。これをフレームワークに組み込むと便利です。

考え?


これは拡張メソッドを使用して行うことができますが、私は同意します。これはコードへの素晴らしい追加であり、Webコンテキストで非常に役立ちます。
Armstrongest

ええ、これは頻繁なシナリオです...特別なメソッドString.IsNullOrEmpty(string)...さえあります
Max Galkin

12
「null-coalesce演算子は空の文字列を検出しません。」まあそれはヌル -coalescingオペレータ、ないnullOrEmpty -coalescing演算子。そして個人的には、2つの値を区別する言語でnull値と空の値を混在させることを軽視します。そして、私は少し強迫的ですので、理由がわかっていても、言語/実装が2つを区別できない場合は不快です([SQLのほとんどの実装]のように)。
JAB、2011

3
??オーバーロードできません:msdn.microsoft.com/en-us/library/8edha89s(v = vs.100).aspx-オーバーロード可能にするのは素晴らしいことです。私は組み合わせを使用します:文字列が空の場合にs1.Nullify() ?? s2.Nullify()where string Nullify(this s)はa nullを返します。
キット

唯一の問題?私は自分が?? =を望んでいるのを見つけ、それを行う方法があるかどうかを確認しているときにこのスレッドを見つけました。(状況:最初のパスで例外ケースが読み込まれました。ここで戻って、まだ読み込まれていないものにデフォルト値を読み込みます。)
Loren Pechtel

3

?? 必要な場合、または3項演算子(ほとんどが使い慣れている)を使用する必要がある場合

意図を最もよく表すものを使用する必要があります。null合体演算子あるので、それを使用してください

一方、あまりに特殊化されているので、他の用途はないと思います。||他の言語がそうであるように、私はオペレーターの適切なオーバーロードを好んだでしょう。これは、言語設計においてより簡潔になります。しかしよく …


3

涼しい!nullの合体演算子について知らなかった人として私を数えてください-それはかなり気の利いたものです。

三項演算子よりもはるかに読みやすいと思います。

私がそれを使用する可能性がある最初に頭に浮かぶのは、すべてのデフォルトパラメータを1つの場所に保持することです。

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}

2

少し奇妙な使用例ですが、IDisposableオブジェクトが引数として渡される可能性がある(したがって、親によって破棄される)メソッドがありましたが、nullである可能性もあります(ローカルメソッドで作成して破棄する必要があります)

それを使用するには、コードは次のようになります

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

しかし、nullの合体により、ずっとすっきりします

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

0

私はこのように使用しました:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.