3

null の可能性があるオブジェクトのプロパティを呼び出せるようにしたいが、呼び出し時に null かどうかを明示的にチェックする必要はありません。

このような:

var something = someObjectThatMightBeNull.Property;

私の考えは、次のような Expression を取るメソッドを作成することです。

var something = GetValueSafe(() => someObjectThatMightBeNull.Property);

TResult? GetValueSafe<TResult>(Expression<Func<TResult>> expression) 
    where TResult : struct
{
    // what must I do?
}

私がする必要があるのは、式を調べて、someObjectThatMightBeNullnull かどうかを判断することです。どうすればいいですか?

怠け者になるためのよりスマートな方法があれば、それもありがたいです。

ありがとう!

4

2 に答える 2

3

複雑ですが、「表現の土地」を離れることなく実行できます。

// Get the initial property expression from the left 
// side of the initial lambda. (someObjectThatMightBeNull.Property)
var propertyCall = (MemberExpression)expression.Body;

// Next, remove the property, by calling the Expression 
// property from the MemberExpression (someObjectThatMightBeNull)
var initialObjectExpression = propertyCall.Expression;

// Next, create a null constant expression, which will 
// be used to compare against the initialObjectExpression (null)
var nullExpression = Expression.Constant(null, initialObjectExpression.Type);

// Next, create an expression comparing the two: 
// (someObjectThatMightBeNull == null)
var equalityCheck = Expression.Equal(initialObjectExpression, nullExpression);

// Next, create a lambda expression, so the equalityCheck 
// can actually be called ( () => someObjectThatMightBeNull == null )
var newLambda = Expression.Lambda<Func<bool>>(equalityCheck, null);

// Compile the expression. 
var function = newLambda.Compile();

// Run the compiled delegate. 
var isNull = function();

そうは言っても、Andras Zoltan がコメントで雄弁に述べているように、「できるからといって、そうすべきであるとは限りません」。これを行う正当な理由があることを確認してください。より良い方法がある場合は、代わりにそれを行います。Andras には優れた回避策があります。

于 2012-04-10T12:38:12.423 に答える
3

あなたが話していることはヌルセーフ逆参照と呼ばれます-このSOは具体的にその質問をします: C# if-null-then-null expression .

式は実際には答えではありません (そのステートメントの理由を明確にするために、以下を参照してください)。ただし、この拡張メソッドは次のようになります。

public static TResult? GetValueSafe<TInstance, TResult>(this TInstance instance,
  Func<TInstance, TResult> accessor)
  where TInstance : class
  where TResult : struct
{  
   return instance != null ? (TResult?)accessor(instance) : (TResult?)null;
}

そして今、あなたはできる:

MyObject o = null;
int? i = o.GetValueSafe(obj => obj.SomeIntProperty);

Assert.IsNull(i);    

明らかに、これはプロパティが構造体の場合に最も便利です。任意の型に縮小して使用することができますが、int、double などをdefault(TResult)取得できます。0

public static TResult GetValueSafe<TInstance, TResult>(this TInstance instance,
  Func<TInstance, TResult> accessor, TResult def = default(TResult))
  where TInstance : class
{  
   return instance != null ? accessor(instance) : def;
}

この 2 番目のバージョンは、任意の に対して機能するため、特に便利ですTResult。オプションのパラメーターを拡張して、呼び出し元がデフォルトを提供できるようにしました。たとえば、(o前のコードを使用して):

int i = o.GetValueSafe(obj => obj.SomeIntProperty); //yields 0
i = o.GetValueSafe(obj => obj.SomeIntProperty, -1); //yields -1

//while this yields string.Empty instead of null
string s = o.GetValueSafe(obj => obj.SomeStringProperty, string.Empty);

編集 - デビッドのコメントに応えて

David は、式ベースのソリューションを提供しないため、私の回答は間違っていると示唆しました。それが求められていたものです。私の要点は、SOに関する真に正しく、実際に責​​任のある回答は、質問をする人にとってより簡単な解決策を常に模索する必要があるということです。単純な問題に対する過度に複雑な解決策は、私たちの日常の職業生活では避けるべきだということは広く受け入れられていると思います。そして SO は、コミュニティが同じように振る舞うので、人気があるだけです。

デビッドはまた、「彼らは解決策ではない」という私の不当な声明にも異議を唱えました。そのため、ここでそれを拡張し、式ベースの解決策がほとんど無意味である理由を示します。実際には求めません(ちなみに、デビッドの答えはどちらもカバーしていません)。

皮肉なことに、この答え自体がおそらく不必要に複雑になるということです:) 式が最適なルートではない理由を実際に気にしない場合は、ここから先は安全に無視できます

これを式で解決できると言うのは正しいですが、質問に示されている例では、それらを使用する理由はまったくありません-最終的には非常に単純な問題が複雑になりすぎています。そして、実行時に式をコンパイルするオーバーヘッド (そして、キャッシュを入れない限り、後でそれを破棄します。これは、DLR が使用するような呼び出しサイトのようなものを発行しない限り、正しく処理するのが難しいでしょう) は、ソリューションと比較して巨大です。ここに提示します。

最終的には、どのような解決策であっても、呼び出し元が必要とする作業を最小限に抑えることが目的ですが、同時に、式アナライザーによって実行される作業も最小限に抑える必要があります。そうしないと、解決策は、多くの作業なしではほとんど解決できなくなります。私の要点を説明するために、 object が与えられた場合に、式を取る静的メソッドで実現できる最も単純なものを見てみましょうo

var i = GetValueSafe(obj => obj.SomeIntProperty);

ええと、その式は実際には何もしません - それはそれに を渡していないからです-自体oは役に立たないのです。したがって、これに対する最初の解決策は、当然、参照を明示的に渡すことです。onull

var i = GetValueSafe(o, obj => obj.SomeIntProperty);

(注 - 拡張メソッドとして記述することもできます)

したがって、静的メソッドの仕事は、最初のパラメーターを取得し、それを呼び出したときにコンパイル済みの式に渡すことです。これは、プロパティが求められている式の型を識別するのにも役立ちます。ただし、そもそも式を使用する理由が完全に無効になります。メソッド自体がプロパティにアクセスするかどうかをすぐに決定できるため、オブジェクトへの参照があるためnullです。したがって、この場合、拡張メソッドのように、(式の代わりに)参照とアクセサーデリゲートを単純に渡す方が簡単で、単純で、高速です。

前述したように、インスタンスを渡さなければならないことを回避する方法があり、それは次のいずれかを実行することです。

var i = GetValueSafe(obj => o.SomeIntProperty);

または

var i = GetValueSafe(() => o.SomeIntProperty);

拡張メソッド バージョンを割愛しています。これにより、メソッドに渡された参照が取得され、参照を取得するとすぐに式を廃止できるためです。

ここでは、メンバーの左側の式の本体に実際のインスタンス (スコープ内のプロパティ、フィールド、またはローカル変数) を表す式を含める必要があることを呼び出し元に依存しています。 nullチェックを行うために実際に具体的な値を取得できるようにします。

まず第一に、これは式パラメーターの自然な使用法ではないため、呼び出し元が混乱する可能性があると思います。また、これを頻繁に使用する場合はキラーになると思われる別の問題もあります。これらの式をキャッシュすることはできません。これは、回避したい「null-ness」を持つインスタンスが式に焼き付けられるたびにそれが渡されます。これは、呼び出しごとに常に式を再コンパイルする必要があることを意味します。それは本当に遅くなるでしょう。式でインスタンスをパラメーター化すると、それをキャッシュできますが、インスタンスを渡す必要がある最初のソリューションになります。繰り返しになりますが、デリゲートを使用できることは既に示しました。

ExpressionVisitorクラスを使用すると、すべてのプロパティ/フィールドの読み取り (およびそのためのメソッド呼び出し) を必要に応じて「安全な」呼び出しに変換できるものを作成するのは比較的簡単です。ただし、次のような安全な読み取りを行うつもりがない限り、これを行うメリットはありませんa.b.c.d。ただし、値型をそれ自体の null 許容バージョンに拡張すると、式ツリーの書き換えでいくつかの頭痛の種が発生する可能性があります。ほとんど誰も理解できない解決策を残します:)

于 2012-04-10T12:28:58.643 に答える