182

私はこのインターフェースを持っているとします。

interface IContact
{
    IAddress address { get; set; }
}

interface IAddress
{
    string city { get; set; }
}

class Person : IPerson
{
    public IContact contact { get; set; }
}

class test
{
    private test()
    {
        var person = new Person();
        if (person.contact.address.city != null)
        {
            //this will never work if contact is itself null?
        }
    }
}

Person.Contact.Address.City != null(これは City が null かどうかを確認するために機能します。)

ただし、Address または Contact または Person 自体が null の場合、このチェックは失敗します。

現在、私が考えることができる1つの解決策はこれでした:

if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)

{ 
    // Do some stuff here..
}

これを行うよりクリーンな方法はありますか?

nullとしてチェックが行われるのは本当に好きではありません(something == null)。代わりに、メソッドのようなことを行う別の良い方法はありsomething.IsNull()ますか?

4

19 に答える 19

235

一般的な方法では、式ツリーを使用して拡張メソッドでチェックできます。

if (!person.IsNull(p => p.contact.address.city))
{
    //Nothing is null
}

完全なコード:

public class IsNullVisitor : ExpressionVisitor
{
    public bool IsNull { get; private set; }
    public object CurrentObject { get; set; }

    protected override Expression VisitMember(MemberExpression node)
    {
        base.VisitMember(node);
        if (CheckNull())
        {
            return node;
        }

        var member = (PropertyInfo)node.Member;
        CurrentObject = member.GetValue(CurrentObject,null);
        CheckNull();
        return node;
    }

    private bool CheckNull()
    {
        if (CurrentObject == null)
        {
            IsNull = true;
        }
        return IsNull;
    }
}

public static class Helper
{
    public static bool IsNull<T>(this T root,Expression<Func<T, object>> getter)
    {
        var visitor = new IsNullVisitor();
        visitor.CurrentObject = root;
        visitor.Visit(getter);
        return visitor.IsNull;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person nullPerson = null;
        var isNull_0 = nullPerson.IsNull(p => p.contact.address.city);
        var isNull_1 = new Person().IsNull(p => p.contact.address.city);
        var isNull_2 = new Person { contact = new Contact() }.IsNull(p => p.contact.address.city);
        var isNull_3 =  new Person { contact = new Contact { address = new Address() } }.IsNull(p => p.contact.address.city);
        var notnull = new Person { contact = new Contact { address = new Address { city = "LONDON" } } }.IsNull(p => p.contact.address.city);
    }
}
于 2013-07-16T09:55:16.977 に答える
48

あなたの場合、人のプロパティを作成できます

public bool HasCity
{
   get 
   { 
     return (this.Contact!=null && this.Contact.Address!= null && this.Contact.Address.City != null); 
   }     
}

ただし、 person が null かどうかを確認する必要があります

if (person != null && person.HasCity)
{

}

他の質問に対して、文字列の場合、この方法で null または空かどうかを確認することもできます。

string s = string.Empty;
if (!string.IsNullOrEmpty(s))
{
   // string is not null and not empty
}
if (!string.IsNullOrWhiteSpace(s))
{
   // string is not null, not empty and not contains only white spaces
}
于 2013-07-16T09:25:01.893 に答える
26

2014 年 4 月 28 日更新: C# vNext の Null 伝播が計画されています


null チェックの伝播よりも大きな問題があります。他の開発者が理解できる読みやすいコードを目指してください。冗長ではありますが、あなたの例は問題ありません。

頻繁に行われるチェックの場合はPerson、プロパティまたはメソッドの呼び出しとしてクラス内にカプセル化することを検討してください。


とはいえ、無償Funcでジェネリックです!

私は決してこれをしませんが、ここに別の選択肢があります:

class NullHelper
{
    public static bool ChainNotNull<TFirst, TSecond, TThird, TFourth>(TFirst item1, Func<TFirst, TSecond> getItem2, Func<TSecond, TThird> getItem3, Func<TThird, TFourth> getItem4)
    {
        if (item1 == null)
            return false;

        var item2 = getItem2(item1);

        if (item2 == null)
            return false;

        var item3 = getItem3(item2);

        if (item3 == null)
            return false;

        var item4 = getItem4(item3);

        if (item4 == null)
            return false;

        return true;
    }
}

呼ばれる:

    static void Main(string[] args)
    {
        Person person = new Person { Address = new Address { PostCode = new Postcode { Value = "" } } };

        if (NullHelper.ChainNotNull(person, p => p.Address, a => a.PostCode, p => p.Value))
        {
            Console.WriteLine("Not null");
        }
        else
        {
            Console.WriteLine("null");
        }

        Console.ReadLine();
    }
于 2013-07-16T09:26:27.477 に答える
15

2番目の質問、

null チェックが (something == null) として行われるのは本当に好きではありません。代わりに、something.IsNull() メソッドのような何かを行う別の良い方法はありますか?

拡張メソッドを使用して解決できます。

public static class Extensions
{
    public static bool IsNull<T>(this T source) where T : class
    {
        return source == null;
    }
}
于 2013-07-16T09:20:42.843 に答える
10

なんらかの理由で、より「オーバーザトップ」なソリューションの 1 つを使用しても構わない場合は、私のブログ投稿で説明されているソリューションを確認してください。式ツリーを使用して、式を評価する前に値が null かどうかを調べます。ただし、許容できるパフォーマンスを維持するために、IL コードを作成してキャッシュします。

このソリューションでは、次のように記述できます。

string city = person.NullSafeGet(n => n.Contact.Address.City);
于 2013-07-16T11:05:51.290 に答える
8

あなたは書ける:

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

その後:

string s = null;
if(s.IsNull())
{

}

時々これは理にかなっています。しかし、個人的にはそのようなことは避けたいと思います...これは、実際にnullであるオブジェクトのメソッドを呼び出すことができる理由が明確ではないためです。

于 2013-07-16T09:21:54.347 に答える
5

method次のように別の方法で実行します。

private test()
{
    var person = new Person();
    if (!IsNull(person))
    {
        // Proceed
              ........

あなたのIsNull methodいる場所

public bool IsNull(Person person)
{
    if(Person != null && 
       Person.Contact != null && 
       Person.Contact.Address != null && 
       Person.Contact.Address.City != null)
          return false;
    return true;
}
于 2013-07-16T09:24:04.170 に答える
3

このような参照チェーンは、たとえば ORM ツールを使用していて、クラスをできるだけ純粋に保ちたい場合に発生する可能性があります。このシナリオでは、うまく回避できないと思います。

次の拡張メソッド「ファミリ」があります。これは、呼び出されたオブジェクトが null であるかどうかをチェックし、そうでない場合は、要求されたプロパティの 1 つを返すか、それを使用していくつかのメソッドを実行します。もちろん、これは参照型に対してのみ機能します。そのため、対応するジェネリック制約があります。

public static TRet NullOr<T, TRet>(this T obj, Func<T, TRet> getter) where T : class
{
    return obj != null ? getter(obj) : default(TRet);
}

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

これらのメソッドは、手動のソリューションと比較してオーバーヘッドをほとんど追加せず (リフレクションも式ツリーもありません)、より適切な構文を実現できます (IMO)。

var city = person.NullOr(e => e.Contact).NullOr(e => e.Address).NullOr(e => e.City);
if (city != null)
    // do something...

またはメソッドで:

person.NullOrDo(p => p.GoToWork());

ただし、コードの長さがあまり変わらないことについては、はっきりと主張できます。

于 2013-07-19T22:07:31.607 に答える
3
try
{
  // do some stuff here
}
catch (NullReferenceException e)
{
}

実際にこれをしないでください。ヌル チェックを実行し、最適なフォーマットを見つけます。

于 2013-07-16T09:59:51.807 に答える
3

これに役立つ拡張機能があります。ValueOrDefault()。ラムダ ステートメントを受け入れて評価し、予想される例外 (NRE または IOE) がスローされた場合は、評価された値または既定値のいずれかを返します。

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the specified default value.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <param name="defaultValue">The default value to use if the projection or any parent is null.</param>
    /// <returns>the result of the lambda, or the specified default value if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection, TOut defaultValue)
    {
        try
        {
            var result = projection(input);
            if (result == null) result = defaultValue;
            return result;
        }
        catch (NullReferenceException) //most reference types throw this on a null instance
        {
            return defaultValue;
        }
        catch (InvalidOperationException) //Nullable<T> throws this when accessing Value
        {
            return defaultValue;
        }
    }

    /// <summary>
    /// Provides a null-safe member accessor that will return either the result of the lambda or the default value for the type.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="input">The input.</param>
    /// <param name="projection">A lambda specifying the value to produce.</param>
    /// <returns>the result of the lambda, or default(TOut) if any reference in the lambda is null.</returns>
    public static TOut ValueOrDefault<TIn, TOut>(this TIn input, Func<TIn, TOut> projection)
    {
        return input.ValueOrDefault(projection, default(TOut));
    }

特定の既定値を取らないオーバーロードは、どの参照型に対しても null を返します。これはあなたのシナリオでうまくいくはずです:

class test
{
    private test()
    {
        var person = new Person();
        if (person.ValueOrDefault(p=>p.contact.address.city) != null)
        {
            //the above will return null without exception if any member in the chain is null
        }
    }
}
于 2013-07-16T14:28:57.833 に答える
2

私の意見では、等価演算子は、参照の等価性を確保するためのより安全で優れた方法ではありません

常に使用することをお勧めしますReferenceEquals(obj, null)。これは常に機能します。一方、等価演算子 (==) はオーバーロードされる可能性があり、参照ではなく値が等しいかどうかをチェックしている可能性があるためReferenceEquals()、より安全で優れた方法であると言えます。

class MyClass {
   static void Main() {
      object o = null;
      object p = null;
      object q = new Object();

      Console.WriteLine(Object.ReferenceEquals(o, p));
      p = q;
      Console.WriteLine(Object.ReferenceEquals(p, q));
      Console.WriteLine(Object.ReferenceEquals(o, p));
   }
}

参照: MSDN 記事Object.ReferenceEquals Method

しかし、null値に対する私の考えもここにあります

  • 一般に、誰かがデータがないことを示そうとしている場合は、null 値を返すことが最善の方法です。

  • オブジェクトが null ではなく空の場合は、データが返されたことを意味しますが、null を返すことは、何も返されなかったことを明確に示します。

  • また、IMO、null を返すと、オブジェクト内のメンバーにアクセスしようとすると null 例外が発生します。これは、バグのあるコードを強調するのに役立ちます。

C# には、次の 2 種類の等価性があります。

  • 参照の等価性と
  • 価値の平等。

型が不変の場合、演算子 == をオーバーロードして、参照の等価性ではなく値の等価性を比較すると便利です。

不変でない型で operator == をオーバーライドすることはお勧めしません。

詳細については、MSDN の記事、Equals() および演算子 == のオーバーロードに関するガイドライン (C# プログラミング ガイド)を参照してください。

于 2013-07-16T09:29:08.470 に答える
1

私は C# が大好きですが、これは、オブジェクト インスタンスを直接操作するときの C++ の好きな点の 1 つです。一部の宣言は単純に null にできないため、null をチェックする必要はありません。

このパイのスライスを C# で取得する最良の方法 (これは、一部の再設計が少し多すぎる可能性があります。その場合は、他の回答を選択してください) を使用することstructです。構造体にインスタンス化されていない「デフォルト」値 (つまり、0、0.0、null 文字列) がある状況に陥ることはありますが、「if (myStruct == null)」をチェックする必要はまったくありません。

もちろん、その用途を理解せずに切り替えることはありません。それらは値型に使用される傾向があり、データの大きなブロックには実際には使用されません-ある変数から別の変数に構造体を割り当てるときはいつでも、実際にデータをコピーする傾向があり、本質的に元の各値のコピーを作成します(キーワードを使用してこれを回避できますref-繰り返しますが、単に使用するのではなく、よく読んでください)。それでも、StreetAddress のようなものには適している可能性があります。null チェックを行いたくないものに対して怠惰に使用することは絶対にありません。

于 2013-07-16T13:39:17.700 に答える
0

メソッドの null チェックを削除する 1 つの方法は、メソッドの機能を別の場所にカプセル化することです。これを行う 1 つの方法は、ゲッターとセッターを使用することです。たとえば、これを行う代わりに:

class Person : IPerson
{
    public IContact contact { get; set; }
}

これを行う:

class Person : IPerson
{
    public IContact contact 
    { 
        get
        {
            // This initializes the property if it is null. 
            // That way, anytime you access the property "contact" in your code, 
            // it will check to see if it is null and initialize if needed.
            if(_contact == null)
            {
                _contact = new Contact();
            }
            return _contact;
        } 
        set
        {
            _contact = value;
        } 
    }
    private IContact _contact;
}

次に、「person.contact」を呼び出すたびに、「get」メソッドのコードが実行され、null の場合は値が初期化されます。

すべてのタイプで null になる可能性のあるすべてのプロパティに、これとまったく同じ方法論を適用できます。このアプローチの利点は、1) インラインで null チェックを行う必要がなくなり、2) コードが読みやすくなり、コピー アンド ペースト エラーが発生しにくくなることです。

ただし、プロパティの 1 つがnull の場合に何らかのアクションを実行する必要がある場合 (つまり、連絡先が null の Person はドメイン内で実際に何かを意味するのでしょうか?)、このアプローチが必要であることに注意してください。助けになるどころか邪魔になる。ただし、問題のプロパティが null であってはならない場合、このアプローチはその事実を非常に明確に表す方法を提供します。

--jtlovetteiii

于 2013-07-18T12:32:09.067 に答える
0

リフレクションを使用して、すべてのクラスでインターフェイスと余分なコードの実装を強制することを避けることができます。静的メソッドを持つ単純なヘルパー クラス。これは最も効率的な方法ではないかもしれません.私に優しくしてください.私は処女です..

public class Helper
{
    public static bool IsNull(object o, params string[] prop)
    {
        if (o == null)
            return true;

        var v = o;
        foreach (string s in prop)
        {
            PropertyInfo pi = v.GetType().GetProperty(s); //Set flags if not only public props
            v = (pi != null)? pi.GetValue(v, null) : null;
            if (v == null)
                return true;                                
        }

        return false;
    }
}

    //In use
    isNull = Helper.IsNull(p, "ContactPerson", "TheCity");

もちろん、小道具名にタイプミスがあると、結果は間違ったものになります (ほとんどの場合)。

于 2013-07-20T10:12:05.733 に答える