35

edit 2015この質問とその回答は、もはや関連性がありません。null伝播演算子(?.)を持つC#6の出現前に質問されました。これにより、この質問とその後の回答で説明されているハックな回避策が不要になります。2015 年以降、C# では Form.ActiveForm?.ActiveControl?.Name を使用する必要があります。


私は .NET での null 伝播の問題について考えていました。これは、次のような醜いコードの繰り返しにつながることがよくあります。

試行 #1 通常のコード:

string activeControlName = null;
var activeForm = Form.ActiveForm;
if (activeForm != null)
{
    var activeControl = activeForm.ActiveControl;
    if(activeControl != null)
    {
        activeControlname = activeControl.Name;
    }
}

Maybe<T> モナド、または何らかの「if not null」拡張メソッドの使用について、StackOverflow でいくつかの議論がありました。

試み #2、拡張方法:

// Usage:
var activeControlName = Form.ActiveForm
                          .IfNotNull(form => form.ActiveControl)
                          .IfNotNull(control => control.Name);

// Definition:
public static TReturn IfNotNull<TReturn, T>(T instance, Func<T, TReturn> getter)
    where T : class
{
    if (instance != null ) return getter(instance);
    return null;
}

私はこれが良いと思いますが、「IfNotNull」とラムダが繰り返されると、構文が少し乱雑になります。私は今、このデザインを検討しています:

試行 #3、拡張メソッドを使用した Maybe<T>

// Usage:
var activeControlName = (from window in Form.ActiveForm.Maybe()
                         from control in window.ActiveControl.Maybe()
                         select control.Name).FirstOrDefault();

// Definition:
public struct Maybe<T> : IEnumerable<T>
      where T : class
{
    private readonly T instance;

    public Maybe(T instance)
    {
        this.instance = instance;
    }

    public T Value
    {
        get { return instance; }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return Enumerable.Repeat(instance, instance == null ? 0 : 1).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public static class MaybeExtensions
{
    public static Maybe<T> Maybe<T>(this T instance)
        where T : class
    {
        return new Maybe<T>(instance);
    }
}

私の質問は次のとおりです:これは拡張メソッドの悪用ですか? 古い通常のヌルチェックよりも優れていますか?

4

8 に答える 8

18

非常に多くの人が独自に名前を選んでいるのは興味深いことですIfNotNull。C# では、これは可能な限り最も賢明な名前でなければなりません。:)

私が SO で見つけた最も早いもの:この (拡張メソッドに基づく) 短縮形を使用することの潜在的な落とし穴

私のもの(上記を無視して):C#でのパイプフォワード

もう 1 つの最近の例:深いラムダ式で null をチェックする方法は?

IfNotNull拡張メソッドが人気がない理由はいくつかあります。

  1. 一部の人々は、thisパラメーターがnull. メソッド名がそれを明確にしている場合、私は同意しません。

  2. 適用範囲が広すぎる拡張機能は、オートコンプリート メニューを乱雑にする傾向があります。これは、名前空間を適切に使用することで回避できるため、名前空間を必要としない人を悩ませることはありません。

IEnumerableLinq キーワードに合わせてどれだけひねることができるかを確認するための実験として、このアプローチも試してみましたが、最終結果は連鎖または生の命令コードよりも読みにくいと思いますIfNotNull

最終的に、1 つの静的メソッド (拡張メソッドではない)を持つ単純な自己完結型Maybeクラスになりました。これは私にとって非常にうまく機能します。しかし、私は小さなチームで働いており、次の年長の同僚が関数型プログラミングやラムダなどに興味を持っているので、彼はそれに気が進まない.

于 2009-07-28T19:13:30.097 に答える
14

私は拡張メソッドのファンですが、これが本当に役立つとは思いません。(モナド バージョンでは) 式の繰り返しがまだありますが、これは単に、Maybe全員に説明する必要があることを意味します。この場合、追加された学習曲線には十分な利点がないようです。

バージョンはIfNotNull少なくとも繰り返しを回避することができますが、実際にはより明確にならずに、まだ少し長すぎると思います.

おそらくいつの日か、null セーフな逆参照演算子を取得するでしょう...


余談ですが、私のお気に入りの半悪な拡張メソッドは次のとおりです。

public static void ThrowIfNull<T>(this T value, string name) where T : class
{
    if (value == null)
    {
        throw new ArgumentNullException(name);
    }
}

これにより、次のようになります。

void Foo(string x, string y)
{
    if (x == null)
    {
        throw new ArgumentNullException(nameof(x));
    }
    if (y == null)
    {
        throw new ArgumentNullException(nameof(y));
    }
    ...
}

の中へ:

void Foo(string x, string y)
{
    x.ThrowIfNull(nameof(x));
    y.ThrowIfNull(nameof(y));
    ...
}

パラメータ名の厄介な繰り返しはまだありますが、少なくともすっきりしています。もちろん、.NET 4.0 では Code Contracts を使用します。これは、私が今書いているものです... Stack Overflow は素晴らしい作業回避策です ;)

于 2009-07-28T18:53:48.093 に答える
1

拡張メソッドでネストされた if を削減したい場合は、次のようにしてみてください。

public static object GetProperty(this object o, Type t, string p)
{
    if (o != null)
    {
        PropertyInfo pi = t.GetProperty(p);
        if (pi != null)
        {
            return pi.GetValue(o, null);
        }
        return null;
    }
    return null;
}

したがって、コードでは次のようにします。

string activeControlName = (Form.ActiveForm as object)
    .GetProperty(typeof(Form),"ActiveControl")
    .GetProperty(typeof(Control),"Name");

リフレクションが遅いため、頻繁に使用するかどうかはわかりません。これが代替手段よりもはるかに優れているとは思いませんが、仕方...

(注:これらのタイプが混同されている可能性があります):)

于 2009-07-28T19:11:33.737 に答える
0

IfNotNull ソリューションが最適です (C# チームが null セーフな逆参照演算子を提供するまでは)。

于 2009-07-28T22:13:08.067 に答える
0

最初のサンプルは機能し、一目で読むのが最も簡単です。それを改善する必要は本当にありますか?

于 2009-07-28T19:20:44.437 に答える
0

確かに、元の 2 ネストされた IF は、他の選択肢よりもはるかに読みやすいです。しかし、問題をより一般的に解決したいことを示唆している場合は、別の解決策があります。

try
{
    var activeForm = Form.ActiveForm; assumeIsNotNull(activeForm);
    var activeControl = activeForm.ActiveControl; assumeIsNotNull(activeControl);
    var activeControlname = activeControl.Name;
}
catch (AssumptionChainFailed)
{
}

どこ

class AssumptionChainFailed : Exception { }
void assumeIsNotNull(object obj)
{
    if (obj == null) throw new AssumptionChainFailed();
}
于 2012-02-27T18:27:28.670 に答える
0

私はどちらのソリューションにもあまり夢中ではありません。オリジナルの短いバージョンの問題点:

string activeControlName = null;
if (Form.ActiveForm != null)
    if (Form.ActiveForm.ActivControl != null) activeControlname = activeControl.Name;

そうでない場合は、NotNullChain または FluentNotNull オブジェクトを記述して、null でないいくつかのテストを連続してチェーンできるようにすることを検討します。null に対して動作する IfNotNull 拡張メソッドが少し奇妙に思えることに同意します - 拡張メソッドは単なるシンタックス シュガーですが。

Mark Synowiec の答えは一般的なものにできると思います。

私見ですが、C# コア チームはこの「問題」に目を向けるべきだと思いますが、もっと大きな問題に取り組む必要があると思います。

于 2010-06-23T03:36:42.190 に答える