プロパティのプロパティがnullかどうかを確認するC#のエレガントな方法


97

C#で、この例ではPropertyCから値をプルしたいとし、ObjectA、PropertyA、PropertyBはすべてnullにすることができます。

ObjectA.PropertyA.PropertyB.PropertyC

最小限のコードでPropertyCを安全に取得するにはどうすればよいですか?

今私はチェックします:

if(ObjectA != null && ObjectA.PropertyA !=null && ObjectA.PropertyA.PropertyB != null)
{
    // safely pull off the value
    int value = objectA.PropertyA.PropertyB.PropertyC;
}

このようなもの(疑似コード)を実行するとよいでしょう。

int value = ObjectA.PropertyA.PropertyB ? ObjectA.PropertyA.PropertyB : defaultVal;

nullコアレッシング演算子でさらに折りたたまれている可能性もあります。

編集元々、2番目の例はjsのようだと言っていましたが、jsでは機能しないことが正しく指摘されていたため、擬似コードに変更しました。


あなたのjsの例がどのように機能するかわかりません。ObjectAまたはPropertyAnullの場合は常に「オブジェクトが予期されています」エラーが発生するはずです。
リンカーン

回答:


117

C#6では、Null条件演算子を使用できます。したがって、元のテストは次のようになります。

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;

2
これが何をするのか説明してもらえますか?がnullのvalue場合PropertyCは何に等しいですか?またはPropertyBnullの場合 Object Anullの場合はどうなりますか?
Kolobキャニオン

1
これらのプロパティのいずれかがnullの場合、ステートメント全体がとして返されnullます。それは左から右に始まります。構文上の砂糖がない場合、これは一連のifステートメントに相当if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......し、最終的な最後の式はif(propertyZ != null) { value = propertyZ }
DetectivePikachu

27

短い拡張方法:

public static TResult IfNotNull<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator)
  where TResult : class where TInput : class
{
  if (o == null) return null;
  return evaluator(o);
}

使用する

PropertyC value = ObjectA.IfNotNull(x => x.PropertyA).IfNotNull(x => x.PropertyB).IfNotNull(x => x.PropertyC);

この単純な拡張メソッドなど、http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/で見つけることができます。

編集:

しばらく使用した後、このメソッドの適切な名前は、元のWith()ではなくIfNotNull()になるはずです。


15

クラスにメソッドを追加できますか?そうでない場合、拡張メソッドの使用を考えましたか?と呼ばれるオブジェクトタイプの拡張メソッドを作成できますGetPropC()

例:

public static class MyExtensions
{
    public static int GetPropC(this MyObjectType obj, int defaltValue)
    {
        if (obj != null && obj.PropertyA != null & obj.PropertyA.PropertyB != null)
            return obj.PropertyA.PropertyB.PropertyC;
        return defaltValue;
    }
}

使用法:

int val = ObjectA.GetPropC(0); // will return PropC value, or 0 (defaltValue)

ちなみに、これは.NET 3以降を使用していることを前提としています。


11

あなたのやり方は正しいです。

あなたは可能性が説明したもののようなトリックを使用し、ここで LINQの式を使用して、:

int value = ObjectA.NullSafeEval(x => x.PropertyA.PropertyB.PropertyC, 0);

しかし、各プロパティを手動でチェックするよりもはるかに遅くなります...


10

デメテル法則を順守するためのリファクタリング


プロパティを読み取るだけの場合、オブジェクトグラフの深さを3レベルだけにしてリファクタリングが必要になるとは考えていません。OPがPropertyCを介して参照されるオブジェクトのメソッドを呼び出したかったが、それが読み取り前にnullのチェックのみを必要とするプロパティである場合はそうではないことに同意します。この例では、CountryがKeyValuePairなどの参照型の場合、Customer.Address.Countryのように単純な場合があります。これをどのようにリファクタリングしてnull参照チェックの必要性を回避しますか?
ダレンルイス

OPの例は、実際には深さが4です。私の提案は、null refチェックを削除することではなく、適切に処理できる可能性が最も高いオブジェクト内でそれらを見つけることです。ほとんどの「経験則」と同様に例外はありますが、これが例外であるとは確信していません。同意しないことに同意できますか?
rtalbot、2010

3
@rtalbotに同意します(ただし、公平に言うと、最後の項目がKeyValuePairであるため、@ Daz Lewisは4深度の例を提案しています)。何かがCustomerオブジェクトを混乱させている場合、Addressオブジェクトの階層構造を介してどのようなビジネスが見られているのかわかりません。後で、KeyValuePairがCountryプロパティにとってそれほど優れたアイデアではなかったと判断したとします。その場合、全員のコードを変更する必要があります。それは良いデザインではありません。
Jeffrey L Whitledge、2010

10

2014年の更新:C#6には、?.「安全なナビゲーション」または「null伝播」と呼ばれるさまざまな新しい演算子があります

parent?.child

読むhttp://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspxの詳細については、

これは長い間、非常に人気のあるリクエスト でしたhttps://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d


8

あなたは明らかにNullable Monadを探しています:

string result = new A().PropertyB.PropertyC.Value;

なる

string result = from a in new A()
                from b in a.PropertyB
                from c in b.PropertyC
                select c.Value;

nullnull可能なプロパティのいずれかがnullの場合、これはを返します。それ以外の場合、の値Value

class A { public B PropertyB { get; set; } }
class B { public C PropertyC { get; set; } }
class C { public string Value { get; set; } }

LINQ拡張メソッド:

public static class NullableExtensions
{
    public static TResult SelectMany<TOuter, TInner, TResult>(
        this TOuter source,
        Func<TOuter, TInner> innerSelector,
        Func<TOuter, TInner, TResult> resultSelector)
        where TOuter : class
        where TInner : class
        where TResult : class
    {
        if (source == null) return null;
        TInner inner = innerSelector(source);
        if (inner == null) return null;
        return resultSelector(source, inner);
    }
}

ここに拡張メソッドがあるのはなぜですか?使用されていません。
Mladen Mihajlovic 2014

1
@MladenMihajlovic:SelectMany拡張メソッドはfrom ... in ... from ... in ...構文で使用されます。
dtb 2014

5

タイプの空の値があるとすると、1つのアプローチは次のようになります。

var x = (((objectA ?? A.Empty).PropertyOfB ?? B.Empty).PropertyOfC ?? C.Empty).PropertyOfString;

私はC#の大ファンですが、新しいJava(1.7?)で非常に良いのは。?オペレーター:

 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;

1
それは本当にJava 1.7になるのでしょうか?それは長い間C#で要求されていましたが、私はそれが起こることはないと思います...
Thomas Levesque

残念ながら空の値はありません。そのJava構文は見栄えがいいですが!構文が欲しいからといって、これに賛成票を投じます!
ジョンクラー

3
Thomas:前回tech.puredanger.com/java7を確認したところ、Javaで取得できることを示唆していました。しかし、今私がそれを再確認すると、次のようになります。だから私は私の声明を取り消して、それを新しいものに置き換えます。それはJava 1.7のために提案されましたが、それをしませんでした。
ちょうど別のメタ

追加のアプローチは、monad.netで使用されているものです
別のメタ

1
?のようです。オペレーターはVisual Studio 2015にありますhttps://msdn.microsoft.com/en-us/library/dn986595.aspx
Edward

4

このコードは「最小限のコード」ですが、ベストプラクティスではありません。

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}

1
このようなコードをたくさん見ましたが、パフォーマンスの低下を無視すると、最大の問題は、実際の例外が何百万もの無用のnull ref例外で溺れてしまうため、デバッグが複雑になることです。
2013年

3年後に自分の答えを読むのは楽しいこともあります。今日は違う答えになると思います。私はコードがデメテルの法則に違反していると私は言うでしょうし、そうしないようにそれをリファクタリングすることをお勧めします。
Boris Modylevsky 2013年

1
元の回答から7年後の今日、私は@Phillip Nganに参加し、次の構文でC#6を使用します。int?値= objectA?.PropertyA?.PropertyB?.PropertyC;
Boris Modylevsky 2017

4

そのような呼び出しをチェーンする必要があるときは、作成したヘルパーメソッドTryGet()を使用します。

    public static U TryGet<T, U>(this T obj, Func<T, U> func)
    {
        return obj.TryGet(func, default(U));
    }

    public static U TryGet<T, U>(this T obj, Func<T, U> func, U whenNull)
    {
        return obj == null ? whenNull : func(obj);
    }

あなたの場合、それを次のように使用します:

    int value = ObjectA
        .TryGet(p => p.PropertyA)
        .TryGet(p => p.PropertyB)
        .TryGet(p => p.PropertyC, defaultVal);

このコードは機能しないと思います。defaultValのタイプは何ですか?var p = new Person(); Assert.AreEqual(p.TryGet(x => x.FirstName).TryGet(x => x.LastName).TryGet(x => x.NickName、 "foo")、 "foo");
キース

私が書いた例は、ObjectA.PropertyA.PropertyB.PropertyCのように読む必要があります。コードが「FirstName」から「LastName」というプロパティを読み込もうとしているようですが、これは意図された使用法ではありません。より正しい例は次のようになります。var postcode = person.TryGet(p => p.Address).TryGet(p => p.Postcode); ちなみに、私のTryGet()ヘルパーメソッドは、C#6.0の新機能であるnull条件演算子と非常によく似ています。その使用法は次のようになります。var postcode = person?.Address?.Postcode; msdn.microsoft.com/en-us/magazine/dn802602.aspx
エマニュエル、

3

新しいC#6.0で何かを見ました。これは '?' チェックする代わりに

たとえば、代わりに

if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)
{ 
  var city = person.contact.address.city;
}

あなたは単に使う

var city = person?.contact?.address?.city;

誰かのお役に立てば幸いです。


更新:

あなたは今このようにすることができます

 var city = (Person != null)? 
           ((Person.Contact!=null)? 
              ((Person.Contact.Address!= null)?
                      ((Person.Contact.Address.City!=null)? 
                                 Person.Contact.Address.City : null )
                       :null)
               :null)
            : null;

1

あなたはこれを行うことができます:

class ObjectAType
{
    public int PropertyC
    {
        get
        {
            if (PropertyA == null)
                return 0;
            if (PropertyA.PropertyB == null)
                return 0;
            return PropertyA.PropertyB.PropertyC;
        }
    }
}



if (ObjectA != null)
{
    int value = ObjectA.PropertyC;
    ...
}

または、これがさらに良いかもしれません:

private static int GetPropertyC(ObjectAType objectA)
{
    if (objectA == null)
        return 0;
    if (objectA.PropertyA == null)
        return 0;
    if (objectA.PropertyA.PropertyB == null)
        return 0;
    return objectA.PropertyA.PropertyB.PropertyC;
}


int value = GetPropertyC(ObjectA);

1

あなたは次の拡張子を使うことができ、それは本当に良いと思います:

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<TF, TR>(TF t, Func<TF, TR> f)
    where TF : class
{
    return t != null ? f(t) : default(TR);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, TR>(T1 p1, Func<T1, T2> p2, Func<T2, TR> p3)
    where T1 : class
    where T2 : class
{
    return Get(Get(p1, p2), p3);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, T3, TR>(T1 p1, Func<T1, T2> p2, Func<T2, T3> p3, Func<T3, TR> p4)
    where T1 : class
    where T2 : class
    where T3 : class
{
    return Get(Get(Get(p1, p2), p3), p4);
}

そしてそれはこのように使用されます:

int value = Nulify.Get(objectA, x=>x.PropertyA, x=>x.PropertyB, x=>x.PropertyC);

0

Nullable型と同様のパターンを使用して、PropertyAの型に独自のメソッド(または、それが自分の型でない場合は拡張メソッド)を記述します。

class PropertyAType
{
   public PropertyBType PropertyB {get; set; }

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}

その場合、明らかにPropertyBがnullになることはありません。
2010

0

この投稿を偶然見つけました。

少し前に、Visual Studio Connectで新しい???オペレーターの追加について提案しました。

http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4104392-add-as-an-recursive-null-reference-check-opera

これにはフレームワークチームの作業が必要ですが、言語を変更する必要はありませんが、コンパイラの魔法をかけるだけです。コンパイラがこのコードを変更するべきだという考えでした(構文はatmは許可されていません)

string product_name = Order.OrderDetails[0].Product.Name ??? "no product defined";

このコードに

Func<string> _get_default = () => "no product defined"; 
string product_name = Order == null 
    ? _get_default.Invoke() 
    : Order.OrderDetails[0] == null 
        ? _get_default.Invoke() 
        : Order.OrderDetails[0].Product == null 
            ? _get_default.Invoke() 
            : Order.OrderDetails[0].Product.Name ?? _get_default.Invoke()

nullチェックの場合、これは次のようになります。

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;

0

デフォルト値を受け入れるメソッドを作成しました。これを使用する方法は次のとおりです。

var teacher = new Teacher();
return teacher.GetProperty(t => t.Name);
return teacher.GetProperty(t => t.Name, "Default name");

これがコードです:

public static class Helper
{
    /// <summary>
    /// Gets a property if the object is not null.
    /// var teacher = new Teacher();
    /// return teacher.GetProperty(t => t.Name);
    /// return teacher.GetProperty(t => t.Name, "Default name");
    /// </summary>
    public static TSecond GetProperty<TFirst, TSecond>(this TFirst item1,
        Func<TFirst, TSecond> getItem2, TSecond defaultValue = default(TSecond))
    {
        if (item1 == null)
        {
            return defaultValue;
        }

        return getItem2(item1);
    }
}

1
このソリューションは、すでに他の回答で(繰り返し)提供されています。再度投稿する理由は全くありません。
2014年

デフォルト値に対応するものはありませんでした。
山本明

定義されたデフォルト値を利用する他の6つを数えます。どうやらあなたはそんなに一生懸命見ていませんでした。
2014年

0

それは不可能。エラーであるnullの逆参照が原因でnullがnullの
ObjectA.PropertyA.PropertyB場合ObjectAは失敗します。

if(ObjectA != null && ObjectA.PropertyA...は短絡のために機能しObjectA.PropertyAます。つまり、の場合ObjectAはチェックされませんnull

あなたが提案する最初の方法は、意図をもって最良かつ最も明確です。どちらかと言えば、非常に多くのヌルに依存することなく、再設計を試みることができます。


-1

ラムダゴブリーグックを乗り越えると、このアプローチはかなり簡単です。

public static TProperty GetPropertyOrDefault<TObject, TProperty>(this TObject model, Func<TObject, TProperty> valueFunc)  
                                                        where TObject : class
    {
        try
        {
            return valueFunc.Invoke(model);
        }
        catch (NullReferenceException nex)
        {
            return default(TProperty);
        }
    }

次のような使用法で:

ObjectA objectA = null;

Assert.AreEqual(0,objectA.GetPropertyOrDefault(prop=>prop.ObjectB.ObjectB.ObjectC.ID));

Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));

私が応答を提供してから8年後に誰かが反対票を投じる理由に興味があるだけです(これはC#6のnullの合体が問題になる前の年でした)。
BlackjacketMack

-3
var result = nullableproperty ?? defaultvalue;

??最初の引数がある場合(nullで合体オペレータ)手段がnull、代わりに第二のいずれかを返します。


2
この答えはOPの問題を解決しません。どのようにしてソリューションを適用しますか?式のすべての部分(ObjectA、PropertyA、およびPropertyB)をnullにできる場合、演算子をObjectA.PropertyA.PropertyBに追加しますか?
Artemix 2013年

確かに、質問もまったく読んだことがないと思います。とにかく、不可能は何もしないだけです:P static void Main(string [] args){a ca = new a(); var default_value = new a(){b = new object()}; var value =(ca ?? default_value).b ?? default_value.b; }クラスa {パブリックオブジェクトb = null; }
AridaneÁlamo2013年

(ObjectA ?? DefaultMockedAtNull).PropertyA!= null?ObjectA.PropertyA.PropertyB:null
AridaneÁlamo2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.