空文字 - c# string null




深いヌルチェック、良い方法はありますか? (11)

1つの選択肢は、Null Object Pattenを使用することです。ケーキを持たないときにnullを持つ代わりに、NullFastingなどを返すNullCakeがあります。申し訳ありませんが、これについては説明していませんが、他の人は、

注:この質問は.?導入前に尋ねられまし.? C#6 / Visual Studio 2015の演算子です。

私たちはすべてそこにいました。cake.frosting.berries.loaderのような深い性質があります。ヌルであるかどうかを確認する必要があります。例外はありません。 方法は、短絡if文を使用することです

if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...

これはまったくエレガントではなく、おそらくチェーン全体をチェックし、変数/プロパティがNULLになるかどうかを調べる簡単な方法があるはずです。

いくつかの拡張メソッドを使用することは可能ですか、それとも言語機能ですか、それともちょっと悪い考えですか?



driisの答えは面白いですが、私はそれがあまりにも高価なパフォーマンス賢明だと思う。 多くのデリゲートをコンパイルするのではなく、プロパティパスごとに1つのラムダをコンパイルし、キャッシュしてから、多くのタイプを再呼び出ししたいと思っています。

以下のNullCoalesceはそれだけで、パスがnullの場合にnullチェックとデフォルト(TResult)の戻り値を持つ新しいラムダ式を返します。

例:

NullCoalesce((Process p) => p.StartInfo.FileName)

式を返す

(Process p) => (p != null && p.StartInfo != null ? p.StartInfo.FileName : default(string));

コード:

    static void Main(string[] args)
    {
        var converted = NullCoalesce((MethodInfo p) => p.DeclaringType.Assembly.Evidence.Locked);
        var converted2 = NullCoalesce((string[] s) => s.Length);
    }

    private static Expression<Func<TSource, TResult>> NullCoalesce<TSource, TResult>(Expression<Func<TSource, TResult>> lambdaExpression)
    {
        var test = GetTest(lambdaExpression.Body);
        if (test != null)
        {
            return Expression.Lambda<Func<TSource, TResult>>(
                Expression.Condition(
                    test,
                    lambdaExpression.Body,
                    Expression.Default(
                        typeof(TResult)
                    )
                ),
                lambdaExpression.Parameters
            );
        }
        return lambdaExpression;
    }

    private static Expression GetTest(Expression expression)
    {
        Expression container;
        switch (expression.NodeType)
        {
            case ExpressionType.ArrayLength:
                container = ((UnaryExpression)expression).Operand;
                break;
            case ExpressionType.MemberAccess:
                if ((container = ((MemberExpression)expression).Expression) == null)
                {
                    return null;
                }
                break;
            default:
                return null;
        }
        var baseTest = GetTest(container);
        if (!container.Type.IsValueType)
        {
            var containerNotNull = Expression.NotEqual(
                container,
                Expression.Default(
                    container.Type
                )
            );
            return (baseTest == null ?
                containerNotNull :
                Expression.AndAlso(
                    baseTest,
                    containerNotNull
                )
            );
        }
        return baseTest;
    }

アップデート: Visual Studio 2015以降、C#コンパイラ(言語バージョン6)は?.認識するようになりました?. 演算子は、 "深いヌルチェック"を簡単にします。 詳細については、 この回答を参照してください。

あなたのコードを再設計することは別として、 この削除された答えが示唆したように、 try…catchブロックを使用して、深いプロパティールックアップ中にNullReferenceExceptionが発生するかどうかを調べるオプションがあります(ひどいですが)。

try
{
    var x = cake.frosting.berries.loader;
    ...
}
catch (NullReferenceException ex)
{
    // either one of cake, frosting, or berries was null
    ...
}

私は個人的に以下の理由でこれをしませんでした:

  • それはいい見えません。
  • それは例外処理を使用します。例外処理は、例外的な状況を対象とし、通常の操作過程で頻繁に発生すると予想されるものではありません。
  • おそらく、 NullReferenceExceptionが明示的に捕捉されるべきではありません。 ( この質問を参照してください)。

だから、いくつかの拡張メソッドを使用することが可能ですか、それは言語の機能になりますか、[...]

これは、C#が既に洗練された遅延評価をしていないか、あるいはあなたがリフレクションを使用したくない場合(おそらくC#6で.??[]演算子の形式で利用可能です)また、パフォーマンスや型安全のためには良い考えではありません)。

単純にcake.frosting.berries.loaderを関数に渡す方法はないので(評価され、ヌル参照例外がスローされます)、次の方法で一般的なルックアップメソッドを実装する必要があります。オブジェクトと検索するプロパティの名前:

static object LookupProperty( object startingPoint, params string[] lookupChain )
{
    // 1. if 'startingPoint' is null, return null, or throw an exception.
    // 2. recursively look up one property/field after the other from 'lookupChain',
    //    using reflection.
    // 3. if one lookup is not possible, return null, or throw an exception.
    // 3. return the last property/field's value.
}

...

var x = LookupProperty( cake, "frosting", "berries", "loader" );

(注:コードが編集されました。)

あなたはすぐにそのようなアプローチでいくつかの問題を参照してください。 まず、型の安全性と単純型のプロパティ値のボクシングを得ることはありません。 第2に、何かがうまくいかなかった場合にnull返すことができ、呼び出し関数でこれをチェックするか、例外をスローする必要があり、開始した場所に戻ります。 第三に、遅いかもしれません。 第四に、それはあなたが始めたものよりも醜いように見えます。

[...]、それとも単なる悪い考えですか?

私は一緒に泊まるだろう:

if (cake != null && cake.frosting != null && ...) ...

Mehrdad Afshariの上記の答えに従ってください。

PS:私がこの答えを書いたとき、私は明らかにラムダ関数の式ツリーを考慮しませんでした。 @driisの答えはこの方向の解決策を見てください。 これは一種の反映にも基づいているため、より簡単な解決法( if (… != null & … != null) … )でも同様に機能しないかもしれませんが、構文の視点。


これを達成する必要がある場合は、次の操作を行います。

使用法

Color color = someOrder.ComplexGet(x => x.Customer.LastOrder.Product.Color);

または

Color color = Complex.Get(() => someOrder.Customer.LastOrder.Product.Color);

ヘルパークラスの実装

public static class Complex
{
    public static T1 ComplexGet<T1, T2>(this T2 root, Func<T2, T1> func)
    {
        return Get(() => func(root));
    }

    public static T Get<T>(Func<T> func)
    {
        try
        {
            return func();
        }
        catch (Exception)
        {
            return default(T);
        }
    }
}

のanswerで示唆されているように、これを回避するアプローチの1つは、拡張メソッドと代理人を使用することです。 それらを使用すると、次のようになります。

int? numberOfBerries = cake
    .NullOr(c => c.Frosting)
    .NullOr(f => f.Berries)
    .NullOr(b => b.Count());

実装は、値型、参照型、null値型に対して機能する必要があるため、面倒です。 Timwiのanswerで完全な実装を見つけることができます。ヌル値をチェックする正しい方法は何ですか? 。


私たちは新しい操作 "?"を追加することを検討しました。 あなたが望むセマンティクスを持つ言語に変換します。 (それは今追加されました;以下を参照してください)。つまり、あなたは

cake?.frosting?.berries?.loader

コンパイラはすべての短絡チェックを生成します。

それはC#4のためのバーを作っていませんでした。多分言語の仮説的な将来のバージョンのために。

更新(2014):? オペレータは次のRoslynコンパイラのリリースをplannedしています。 オペレータの正確な統語論的意味論的分析についてはまだ議論があることに注意してください。

更新(2015年7月): Visual Studio 2015がリリースされ、 null条件付き演算子をサポートするC#コンパイラが付属しています?. ?[]


私はObjective-Cのアプローチが好きです:

Objective-C言語はこの問題に対するもう1つのアプローチをとり、nilでメソッドを呼び出すのではなく、そのような呼び出しのすべてに対してnilを返します。

if (cake.frosting.berries != null) 
{
    var str = cake.frosting.berries...;
}

私は、この拡張が深いネスティングシナリオにとって非常に有用であることを発見しました。

public static R Coal<T, R>(this T obj, Func<T, R> f)
    where T : class
{
    return obj != null ? f(obj) : default(R);
}

これは、C#とT-SQLのヌル併合演算子から派生した考えです。 良いことは、戻り値の型は常に内部プロパティの戻り値の型であるということです。

あなたがこれを行うことができる方法:

var berries = cake.Coal(x => x.frosting).Coal(x => x.berries);

...または上記のわずかなバリエーション:

var berries = cake.Coal(x => x.frosting, x => x.berries);

私が知っている最高の構文ではありませんが、うまくいきます。


私はこの最後の夜を掲示し、その後、友人がこの質問に私を指摘した。 それが役に立てば幸い。 次のようなことができます:

var color = Dis.OrDat<string>(() => cake.frosting.berries.color, "blue");


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace DeepNullCoalescence
{
  public static class Dis
  {
    public static T OrDat<T>(Expression<Func><T>> expr, T dat)
    {
      try
      {
        var func = expr.Compile();
        var result = func.Invoke();
        return result ?? dat; //now we can coalesce
      }
      catch (NullReferenceException)
      {
        return dat;
      }
    }
  }
}

ここのブログの記事を読んでください

同じ友人もあなたがこれ見ていることを示唆しました。


私はしばしばより単純な構文を望んでいました! 余分な変数が必要になるため、nullの可能性のあるメソッド戻り値があると、特に醜いことになります(例: cake.frosting.flavors.FirstOrDefault().loader

しかし、ここでは私が使用するかなりまともな代替:ヌルセーフチェーンヘルパーメソッドを作成します。 私はこれが上記の@ Johnの答え( Coal拡張方法)とかなり似ているが、それはより簡単でタイピングが少ないことがわかった。 以下はその様子です:

var loader = NullSafe.Chain(cake, c=>c.frosting, f=>f.berries, b=>b.loader);

実装は次のとおりです。

public static TResult Chain<TA,TB,TC,TResult>(TA a, Func<TA,TB> b, Func<TB,TC> c, Func<TC,TResult> r) 
where TA:class where TB:class where TC:class {
    if (a == null) return default(TResult);
    var B = b(a);
    if (B == null) return default(TResult);
    var C = c(B);
    if (C == null) return default(TResult);
    return r(C);
}

私はまた、いくつかのオーバーロード(2〜6つのパラメータを持つ)と、値型またはデフォルトでチェーンを終了させるオーバーロードを作成しました。 これは私のために本当にうまくいく!





null