もちろん、拡張メソッドを作成する場合は、null
で呼び出すことができます。ただし、インスタンスメソッドの呼び出しとは異なり、nullで呼び出すことはできませんhaveNullReferenceException
をスローするには、手動でチェックしてスローする必要があります。
Linq拡張メソッドの実装について、Any()
マイクロソフトはArgumentNullException
( https://github.com/dotnet/corefx/blob/master/src /System.Linq/src/System/Linq/AnyAll.cs )。
if( myCollection != null && myCollection.Any() )
と書かなくてはならない
このコードのクライアントとして、それを期待するのは間違っていますか? ((int[])null).Any()
はfalse
を返す必要がありますか?
じゃがいもが5個入った袋を持っています。バッグに.Any()
ポテトは入っていますか?
「はい」とあなたは言う。 _<= true
_
じゃがいもを全部取り出して食べる。バッグに.Any()
ポテトは入っていますか?
「いいえ」とあなたは言う。 _<= false
_
袋を火の中で完全に焼却します。バッグに.Any()
ジャガイモは入っていますか?
「バッグはありません。」 _<= ArgumentNullException
_
まず、ソースコードはArgumentNullException
ではなくNullReferenceException
をスローするようです。
とはいえ、多くの場合、このコードはコレクションが既に存在することを知っているコードからのみ呼び出されるため、コレクションがnullではないことがすでにわかっているため、nullチェックを頻繁に行う必要はありません。しかし、それが存在することを知らない場合は、Any()
を呼び出す前にチェックすることは意味があります。
このコードのクライアントとして、それを期待するのは間違っていますか? _
((int[])null).Any()
_はfalse
を返す必要がありますか?
はい。 Any()
が回答する質問は、「このコレクションに要素が含まれていますか?」です。このコレクションが存在しない場合、質問自体は無意味です。存在しないため、何も含むことも含まないこともできません。
Nullは、要素がないのではなく、情報が不足していることを意味します。
Nullをより広く回避することを検討してください。たとえば、組み込みの空の列挙型の1つを使用して、nullの代わりに要素のないコレクションを表します。
状況によってnullを返す場合は、空のコレクションを返すように変更する場合があります。 (そうでなければ、nullがライブラリメソッド(自分のものではない)によって返されているのを見つけた場合、それは残念ですが、ラップして正規化します。)
参照 https://stackoverflow.com/questions/1191919/what-does-linq-return-when-the-results-are-empty
null-conditional syntax 以外に、この問題を緩和する別の方法があります。変数をnull
のままにしないでください。
コレクションをパラメーターとして受け入れる関数について考えてみます。関数の目的でnull
とemptyが同等である場合、最初にnull
が含まれないようにすることができます。
public MyResult DoSomething(int a, IEnumerable<string> words)
{
words = words ?? Enumerable.Empty<string>();
if (!words.Any())
{
...
他のいくつかのメソッドからコレクションをフェッチするときにも同じことができます。
var words = GetWords() ?? Enumerable.Empty<string>();
(GetWords
のような関数を制御できる場合、null
は空のコレクションと同等であることに注意してください。最初に空のコレクションを返すことをお勧めします。)
これで、コレクションに対して任意の操作を実行できます。これは、コレクションがnull
の場合に失敗する多くの操作を実行する必要がある場合に特に役立ちます。空の列挙型をループまたは照会することで同じ結果が得られる場合は、if
条件全体。
このコードのクライアントとして、それを期待するのは間違っていますか? ((int [])null).Any()はfalseを返す必要がありますか?
はい、単にC#を使用していて、その動作が明確に定義され、文書化されているためです。
独自のライブラリを作成している場合、または例外カルチャが異なる別の言語を使用している場合は、falseを期待する方が合理的です。
個人的には、falseを返す方がプログラムをより堅牢にする安全なアプローチであるかのように感じますが、少なくとも議論の余地はあります。
Nullチェックの繰り返しが気になる場合は、独自の 'IsNullOrEmpty()'拡張メソッドを作成して、その名前でStringメソッドをミラーリングし、nullチェックと.Any()呼び出しの両方を1つの呼び出しにラップすることができます。
それ以外の場合、質問のコメントで26の@ 17によって言及されている解決策は、「標準」の方法よりも短く、新しいnull条件構文に詳しい人には合理的に明らかです。
if(myCollection?.Any() == true)
このコードのクライアントとして、それを期待するのは間違っていますか? ((int [])null).Any()はfalseを返す必要がありますか?
期待について疑問に思うなら、意図について考えなければなりません。
null
はEnumerable.Empty<T>
とは非常に異なるものを意味しますErik Eidtの回答 で述べたように、null
と空のコレクションの間には意味の違いがあります。
最初に、それらがどのように使用されることになっているのかを見てみましょう。
本 Framework Design Guidelines:Conventions、Idioms、and Patterns for Reusable .NET Libraries、2nd Edition マイクロソフトアーキテクト Krzysztof CwalinaおよびBrad Abramsは、次のベストプラクティスを述べています。
Xコレクションプロパティまたはコレクションを返すメソッドからnull値を返さないでください。代わりに空のコレクションまたは空の配列を返します。
最終的にデータベースからデータを取得するメソッドを呼び出すことを検討してください。空の配列またはEnumerable.Empty<T>
を受け取った場合、これは単にサンプルスペースが空であること、つまり結果を意味します空のセットです。ただし、このコンテキストでnull
を受信すると、エラー状態を意味します。
ダンウィルソンのジャガイモの類推 と同じ考え方で、データが空のセットであっても、データについて質問することは理にかなっています。 設定がない場合の場合は、かなり少なくなります。
null
とemptyが異なる理由を説明する多くの回答があり、十分に意見がexplainして、それらを異なる方法で処理する必要があるかどうかについて説明します。しかし、あなたが求めているのは:
このコードのクライアントとして、私はそれを期待するのは間違っていますか? ((int [])null).Any()はfalseを返す必要がありますか?
完全に合理的な期待です。あなたは、現在の振る舞いを擁護している誰かと同じようにrightです。現在の実装哲学に同意しますが、推進要因はコンテキスト外の考慮に基づくだけではありません。
述語のないAny()
が本質的にCount() > 0
であることを考えると、このスニペットから何を期待できますか?
List<int> list = null;
if (list.Count > 0) {}
またはジェネリック:
List<int> list = null;
if (list.Foo()) {}
NullReferenceException
を期待していると思います。
Any()
は拡張メソッドであり、extendedオブジェクトとスムーズに統合するである場合、例外をスローすることは最も驚くべきことではありません。Enumerable.Any(null)
を呼び出すことができ、そこにArgumentNullException
を確実に期待できます。それは同じ方法であり、フレームワークの他のほとんどすべてと一致している必要があります。null
オブジェクトへのアクセスはプログラミングエラーであり、フレームワークはnullをマジック値として強制するべきではありません。この方法で使用する場合は、対処する必要があります。Count()
が簡単に判断できる場合、Where()
はそうではありません。 Max()
はどうですか? EMPTYリストの場合は例外がスローされますが、null
の場合もスローされませんか?null
がvalid値(たとえば、String.IsNullOrEmpty()
)である場合にLINQの前にライブラリ設計者が行ったのは、明示的なメソッドを導入することでした。 既存の設計哲学と一致。そうは言っても、書くのはかなり簡単ですが、2つのメソッドEmptyIfNull()
とEmptyOrNull()
は便利かもしれません。
ジムはすべての袋にジャガイモを残すことになっています。そうでなければ私は彼を殺すつもりです。
ジムには、5つのジャガイモが入ったバッグがあります。バッグに.Any()
ポテトは入っていますか?
「はい」とあなたは言う。 <= true
よし、今回はジムが住んでいます。
ジムはジャガイモをすべて取り出して食べます。バッグに.Any()ポテトは入っていますか?
「いいえ」とあなたは言う。 <= false
ジムを殺す時間
ジムは袋を完全に焼却します。バッグに.Any()ポテトは入っていますか?
「バッグはありません。」 <= ArgumentNullException
ジムは生きるか死ぬべきか?まあ、これは予想していなかったので、判決が必要です。ジムにこれを回避させることはバグですか?
注釈を使用する を使用して、この方法でヌルシェナンを我慢していないことを知らせることができます。
public bool Any( [NotNull] List bag )
しかし、ツールチェーンはそれをサポートする必要があります。つまり、おそらくチェックを書くことになります。
これがあなたをそんなに悩ませるなら、私は簡単な拡張方法を提案します。
static public IEnumerable<T> NullToEmpty<T>(this IEnumerable<T> source)
{
return (source == null) ? Enumerable.Empty<T>() : source;
}
これでこれを行うことができます:
List<string> list = null;
var flag = list.NullToEmpty().Any( s => s == "Foo" );
...そしてフラグはfalse
に設定されます。
これはC#の拡張メソッドとその設計哲学についての質問なので、この質問に答える最善の方法は引用することだと思います 拡張メソッドの目的に関するMSDNのドキュメント :
拡張メソッドを使用すると、新しい派生型を作成したり、再コンパイルしたり、元の型を変更したりすることなく、既存の型にメソッドを「追加」できます。拡張メソッドは特別な種類の静的メソッドですが、拡張型のインスタンスメソッドであるかのように呼び出されます。 C#、F#、およびVisual Basicで記述されたクライアントコードの場合、拡張メソッドの呼び出しと、型で実際に定義されているメソッドの呼び出しとの間に明らかな違いはありません。
…
一般に、拡張メソッドは慎重に、必要な場合にのみ実装することをお勧めします。可能な限り、既存の型を拡張する必要があるクライアントコードは、既存の型から派生した新しい型を作成することによって拡張する必要があります。詳細については、継承を参照してください。
拡張メソッドを使用して、ソースコードを変更できない型を拡張する場合、型の実装の変更により拡張メソッドが破損するリスクがあります。
特定のタイプの拡張メソッドを実装する場合は、次の点に注意してください。
- 型で定義されたメソッドと同じシグネチャを持つ拡張メソッドは呼び出されません。
- 拡張メソッドは、名前空間レベルでスコープに組み込まれます。たとえば、
Extensions
という名前の単一の名前空間に拡張メソッドを含む複数の静的クラスがある場合、それらはすべてusing Extensions;
ディレクティブによってスコープに入れられます。
要約すると、拡張メソッドは、開発者が直接追加できない場合でも、特定の型にインスタンスメソッドを追加するように設計されています。そして、インスタンスメソッドはalways存在する場合、拡張メソッドをオーバーライドするため(インスタンスメソッド構文を使用して呼び出される場合)、これはのみである必要がありますメソッドを直接追加したり、クラスを拡張したりできない場合に実行します。*
つまり、拡張メソッドはインスタンスメソッドと同じように動作する必要があります。これは、クライアントによってインスタンスメソッドになってしまう可能性があるためです。また、インスタンスメソッドは、呼び出されているオブジェクトがnull
であればスローする必要があるため、拡張メソッドもスローする必要があります。
*補足として、これはまさにLINQの設計者が直面した状況です。C#3.0がリリースされたとき、コレクションとforeach
ループの両方でSystem.Collections.IEnumerable
とSystem.Collections.Generic.IEnumerable<T>
を使用しているクライアントはすでに数百万に達しています。 。これらのクラスは、2つのメソッドIEnumerator
とCurrent
のみを持つMoveNext
オブジェクトを返したため、Count
、Any
などは、これらの数百万のクライアントを壊します。したがって、この機能を提供するために(特にCurrent
とMoveNext
に関して比較的簡単に実装できるため)、拡張機能メソッドとしてリリースしました。これは現在のすべての機能に適用できます。既存のIEnumerable
インスタンスであり、クラスによってより効率的な方法で実装することもできます。 C#の設計者が初日にLINQをリリースすることを決定した場合、それはIEnumerable
のインスタンスメソッドとして提供され、おそらくこれらのメソッドのデフォルトのインターフェイス実装を提供するために何らかのシステムを設計することになります。
ここでの顕著な点は、例外をスローする代わりにfalseを返すことで、コードの将来のリーダー/モディファイアに関連する可能性のある情報を難読化していることだと思います。
リストがnullになる可能性がある場合は、別のロジックパスを使用することをお勧めします。そうしないと、将来、誰かがリストに基づくロジック(追加など)をifのelse {}に追加して、彼らが予測する理由がないという望ましくない例外。
毎回、読みやすさと保守性が「余分な条件を記述しなければならない」という問題を上回っています。
一般的なルールとして、ほとんどのコードを作成しますが、ほとんどの場合 Say No to Null (および他の同様の投稿)で概説されている理由により、呼び出し元がnullデータを渡さない責任があると想定しています。 nullの概念はよく理解されていないことがよくあります。そのため、実際的な限り、事前に変数を初期化することをお勧めします。妥当なAPIを使用していると想定すると、null値を返さないことが理想的です。そのため、nullをチェックする必要はありません。 nullのポイントは、他の回答で述べられているように、続行する前に操作できる有効なオブジェクト(「バッグ」など)があることを確認することです。これはAny
の機能ではなく、language自体の機能です。 nullオブジェクトは存在しないため、ほとんどの操作を実行できません。私は車を持っていないので、車で店まで車を運転するように頼むようなものなので、車を使って車で店まで行く方法はありません。文字通り存在しないものに対して操作を実行するのは無意味です。
Nullを使用する実用的なケースがあります。たとえば、要求された情報が文字通り利用できない場合などです(たとえば、私の年齢を尋ねた場合、この時点で回答する可能性が最も高いのは「わからない」です。 "、これはnull値です)。ただし、ほとんどの場合、少なくとも変数が初期化されているかどうかを知っている必要があります。そうでない場合は、コードを厳しくする必要があることをお勧めします。プログラムや関数で最初に行うことは、使用する前に値を初期化することです。変数がnullでないことを保証できるため、null値を確認する必要があることはまれです。これは慣れるのに良い習慣であり、変数を初期化することを頻繁に覚えるほど、nullをチェックする必要が少なくなります。