C#でnullでない場合のメソッド呼び出し


106

どういうわけかこの声明を短くすることは可能ですか?

if (obj != null)
    obj.SomeMethod();

たまたまこれをたくさん書いていて、かなり面倒です。私が考えることができる唯一のことは、ヌルオブジェクトパターンを実装することですが、それはいつも私ができることではなく、それは確かに構文を短くする解決策ではありません。

そして、イベントに関する同様の問題、

public event Func<string> MyEvent;

次に呼び出す

if (MyEvent != null)
    MyEvent.Invoke();

41
C#4に新しい演算子を追加することを検討しました。「obj.?SomeMethod()」は、「objがnullでない場合はSomeMethodを呼び出し、それ以外の場合はnullを返す」ことを意味します。残念ながらそれは私たちの予算に合わなかったので、それを実装することはありませんでした。
Eric Lippert、

@Eric:このコメントはまだ有効ですか?私はそれをどこかで見ました、それは4.0で利用可能ですか?
CharithJ、

@CharithJ:いいえ。これは実装されていません。
Eric Lippert、

3
@CharithJ:ヌル合体演算子の存在を知っています。ダースが望んでいることはしません。彼は、null化可能なメンバーアクセスオペレーターを求めています。(ちなみに、以前のコメントでは、nullの合体演算子の特性が正しくありません。「v = m == null?y:m.Valueはv = m ?? yと書くことができる」と言うつもりでした。)
Ericリッペルト

4
新しいリーダーの場合:C#6.0は?。を実装しているので、x?.y?.z?.ToString()は、x、y、またはzがnullの場合はnullを返し、nullがない場合はz.ToString()を返します。
デビッド

回答:


162

C#6以降では、次のように使用できます。

MyEvent?.Invoke();

または:

obj?.SomeMethod();

?.nullで伝播する演算子であり、そして原因となります.Invoke()オペランドがあるときに短絡されますnull。オペランドは1回だけアクセスされるため、「チェックと呼び出しの間の値の変更」問題のリスクはありません。

===

C#6より前のバージョンでは、1つを除いて、nullセーフマジックはありません。拡張メソッド-例:

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

今これは有効です:

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

イベントの場合、これには競合状態を取り除くという利点があります。つまり、一時的な変数は必要ありません。したがって、通常は次のものが必要です。

var handler = SomeEvent;
if(handler != null) handler(this, EventArgs.Empty);

しかし:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

簡単に使用できます:

SomeEvent.SafeInvoke(this); // no race condition, no null risk

1
これについてはほとんど矛盾していません。アクションまたはイベントハンドラ(通常はより独立しています)の場合、これはある程度の意味があります。ただし、通常の方法ではカプセル化を解除しません。これは別の、静的クラスのメソッドを作成する意味と私はカプセル化と分解読みやすさを失うことはないと思う/コードの組織は、全体的な価値は、地元の読みやすさに若干の改善である
tvanfosson

@tvanfosson-確かに; しかし、私の指摘は、これがどこで機能するかを知っている唯一のケースだということです。そして、質問自体がデリゲート/イベントのトピックを提起します。
Marc Gravell

そのコードは、どこかで匿名メソッドを生成することになり、例外のスタックトレースを本当に混乱させます。無名メソッドに名前を付けることは可能ですか?;)
09年

2015 / C#6.0に従って間違っています...?彼は独り言です。ReferenceTHatMayBeNull?.CallMethod()は、nullの場合、メソッドを呼び出しません。
TomTom 2016年

1
@mercuそれは-VB14以降?.で-Marc
Gravell

27

あなたが探しているのは、ヌル条件付き(「合体」ではない)演算子です?.。C#6以降で利用可能です。

あなたの例はでしょうobj?.SomeMethod();。objがnullの場合、何も起こりません。メソッドに引数がある場合、たとえばobj?.SomeMethod(new Foo(), GetBar());、引数がobjnullの場合、引数は評価されません。これは、引数の評価に副作用がある場合に重要です。

そして連鎖が可能です: myObject?.Items?[0]?.DoSomething()


1
これは素晴らしいです。これはC#6の機能であることは注目に値します...(これはVS2015ステートメントから暗示されますが、注目に値します)。:)
カイル・グード

10

簡単な拡張方法:

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

例:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

または代わりに:

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

例:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));

私は拡張メソッドでそれをやろうとしましたが、ほとんど同じコードで終わってしまいました。ただし、この実装の問題は、値型を返す式では機能しないことです。したがって、そのための2番目の方法が必要でした。
orad 2014

4

イベントは、削除されない空のデフォルトデリゲートで初期化できます。

public event EventHandler MyEvent = delegate { };

ヌルチェックは必要ありません。

[ 更新、これを指摘してくれたBevanに感謝]

ただし、パフォーマンスへの影響の可能性に注意してください。私が行った簡単なマイクロベンチマークは、「デフォルトのデリゲート」パターンを使用すると、サブスクライバーなしのイベントの処理が2〜3倍遅くなることを示しています。(私のデュアルコア2.5 GHzラップトップでは、279ミリ秒:5,000万のサブスクライブされていないイベントを発生させるために785ミリ秒を意味します。)アプリケーションのホットスポットの場合、それは考慮すべき問題になる可能性があります。


1
それで、空のデリゲートを呼び出すことでnullチェックを回避します...いくつかのキーストロークを節約するためにメモリと時間の両方の測定可能な犠牲?YMMV、しかし私にとっては、貧弱なトレードオフです。
Bevan

また、複数のデリゲートがサブスクライブされているイベントを呼び出すのは、1つだけのイベントを呼び出すよりもかなりコストがかかることを示すベンチマークも見ました。
グレッグD


2

イアン・グリフィスによるこの記事は、彼があなたが使うべきではないきちんとしたトリックであると彼が結論付けた問題に対する2つの異なる解決策を提供します。


3
そして、その記事の著者として、C#6がnull条件演算子(?。および。[])を使用してこれをすぐに解決するので、絶対に使用しないでください。
イアングリフィス

2

提案されているような拡張方式は、競合状態の問題を実際に解決するのではなく、非表示にする方法です。

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

述べたように、このコードは一時変数を使用したソリューションと同等のエレガントですが...

両方の問題で、イベントのサブスクライブが、イベントからサブスクライブ解除された後に呼び出される可能性があります。デリゲートインスタンスが一時変数にコピーされた後(または上記のメソッドでパラメーターとして渡された後)、デリゲートが呼び出される前にサブスクリプションが解除される可能性があるため、これは可能です。

一般に、このような場合のクライアントコードの動作は予測できません。コンポーネントの状態では、イベント通知を処理できませんでした。それを処理する方法でクライアントコードを書くことは可能ですが、それはクライアントに不必要な責任を負わせるでしょう。

スレッドの安全性を保証する唯一の既知の方法は、イベントの送信者に対してロックステートメントを使用することです。これにより、すべてのサブスクリプション\サブスクリプション解除\呼び出しが確実にシリアル化されます。

より正確にするには、add \ removeイベントアクセサーメソッドで使用されるのと同じ同期オブジェクトにロックを適用する必要があります。これはデフォルトの「this」です。


これは、参照される競合状態ではありません。競合状態はif(MyEvent!= null)// MyEventがnullではないMyEvent.Invoke(); // MyEventがnullになり、問題が発生するこのため、この競合状態では、動作が保証されている非同期イベントハンドラーを作成できません。ただし、「競合状態」があれば、動作が保証されている非同期イベントハンドラーを作成できます。もちろん、サブスクライバーとサブスクライバーへの呼び出しは同期的である必要があります。そうでない場合は、ロックコードが必要です。
jyoung 2009年

確かに。この問題の私の分析はここに投稿されています: blogs.msdn.com/ericlippert/archive/2009/04/29/…–
Eric Lippert


1

多分良くないかもしれませんが、私の意見では、より読みやすいのは拡張メソッドを作成することです

public static bool IsNull(this object obj) {
 return obj == null;
}

1
どういうreturn obj == null意味ですか。それが何を返すか
Hammad Khan

1
ということobjnull、メソッドがを返すかどうかtrueだと思います。
Joel

1
これは質問にどのように答えますか?
Kugel

返信は遅くなりますが、これでうまくいくでしょうか?あるオブジェクトで拡張メソッドを呼び出すことができますnullか?これは型自体のメソッドでは機能しないと確信しているので、拡張メソッドでも機能するかどうかは疑問です。私は、オブジェクトがかどうかを確認するための最良の方法と信じているが nullありますobj is null。残念ながら、オブジェクトがかどうかを確認することはないではないことはnull不幸である、カッコ内のラッピングが必要です。
natiiix

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.