Any()がnull参照例外をスローしないことを期待するのは理不尽ですか?


41

拡張メソッドを作成する場合、もちろん、それを呼び出すことができnullます。ただし、インスタンスメソッドの呼び出しとは異なり、nullで呼び出す場合、aをスローする必要はありませんNullReferenceException->手動でチェックしてスローする必要があります。

Linq拡張メソッドの実装について、Any()MicrosoftはArgumentNullExceptionhttps://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/AnyAll.cs)をスローすることを決定しました。

書かなければならないのはイライラする if( myCollection != null && myCollection.Any() )

このコードのクライアントとして、例えば((int[])null).Any()戻るべきだと期待するのは間違っていますfalseか?


41
C#6では、チェックをif(myCollection?.Any()== true)
17 of 26

5
他の.NET言語との相互運用性を除いて実際にnullを使用しないF#でも、null |> Seq.isEmptythrowsを使用しSystem.ArgumentNullException: Value cannot be nullます。期待されるのは、存在すると予想されるものに対して未定義の値を渡さないことであるため、nullがある場合は例外です。初期化の問題であれば、の代わりに空のシーケンスで開始しnullます。
アーロンM.エシュバッハ

21
そのためnull、コレクションを処理するときに戻るべきではなく、そのような場合は空のコレクションを使用する必要があります。
バクリウ

31
そもそもなぜそれがヌルなのでしょうか?nullを空としてカウントしたくないのは、空ではないからです。まったく違うものです。あなたの場合は空としてカウントしたいかもしれませんが、その種のことを言語に追加するとバグにつながります。
user253751

22
すべての LINQメソッドがnull引数をスローすることを指摘する必要があります。Anyただ一貫しています。
セバスチャンレッド

回答:


157

5個のジャガイモが入った袋があります。.Any()袋にジャガイモは入っていますか?

はい」とあなたは言います。<= true

私はすべてのジャガイモを取り出して食べます。.Any()袋にジャガイモは入っていますか?

いいえ」とあなたは言います。<= false

私はバッグを火で完全に焼却します。.Any()袋にジャガイモが入っていますか?

バッグはありません。」<= ArgumentNullException


3
しかし、空虚に、袋の中にジャガイモはありませんか?FalseはFalseを意味します...(私は同意しません、2番目の視点)。
D.ベンノーブル

6
実際には、誰かがあなたに未知のソースからの閉じた箱を渡します。箱の中の袋にはジャガイモが入っていますか?私は2つのシナリオにのみ興味があります-A)ジャガイモが入っている箱の中に袋があります、またはB)ジャガイモおよび/または箱の中に袋がありません。意味的に、はい、nullとemptyには違いがありますが、99%は気にしません。
デイブティーベン

6
生のジャガイモを5個食べましたか?直ちに医師の診察を受けてください。
オリー

3
@Oly Danは、火を食べて袋を焼却する前に、火でそれらを調理しました。
イスマエルミゲル

3
@ D.BenKnoble:私の車のトランクを見たことがなければ、私の車のトランクに死体がないことを証言してもらえますか?番号?トランクをチェックすることと何も見つからないこと、またはトランクをチェックしないことの違いは何ですか?どちらの場合でもまったく同じ量の死体を見たことがあるので、今すぐ証言することはできませんか?...それが違いです。そもそも実際に確認できない場合、何かを保証することはできません。この2つは平等であると仮定しますが、それは当然のことではありません。場合によっては、2つの間に重要な違いがあるかもしれません。
18

52

まず、ソースコードはArgumentNullExceptionでなくをスローするようNullReferenceExceptionです。

とはいえ、多くの場合、このコードはコレクションが既に存在することを知っているコードからのみ呼び出されるため、コレクションがnullではないことをすでに知っているので、nullチェックを頻繁に入れる必要はありません。しかし、それが存在することを知らない場合は、呼び出す前に確認するのが理にかなってAny()います。

このコードのクライアントとして、例えば((int[])null).Any()戻るべきだと期待するのは間違っていますfalseか?

はい。質問Any()の答えは、「このコレクションは、すべての要素が含まれているのですか?」され このコレクションが存在しない場合、質問自体は無意味です。存在しないので、何も含むことも含まないこともできません。


18
@ChrisWohlert:あなたの直観は私に興味をそそります。また、意味的には、存在しない人は空の人であり、その名前は空の文字列であり、年齢はゼロなどだと思いますか?そして、あなたが含むnullセットは、空のセットを含むセットと同等であると思いますか?
ruakh

17
@ChrisWohlert:明らかに、セットには空のセットを含めることができます。しかし、あなたはそれを信じているようで、同等{ null }{ {} }なければなりません。それは魅力的だと思います。私はそれとはまったく関係がありません。
ruakh

9
コレクション上で何らかの形でコレクションを作成する必要.Addがありnullますか?
マシュー

13
@ChrisWohlert「Aは空のセットです。Bはスイカを含むセットです。Aは空ですか?はい。Bは空ですか?いいえ。Cは空ですか?Uhhh ...」
user253751

16
論点:集合論では、存在しない集合が空集合であるということではありません。空のセットが存在し、存在しないセットは存在しません(実際、存在しないため、まったく存在しません)。
James_pic

22

Nullは、要素ではなく情報の欠落を意味します。

nullをより広く回避することを検討することもできます。たとえば、nullの代わりに、組み込みの空の列挙可能変数の1つを使用して、要素のないコレクションを表します。

状況によってnullを返す場合は、空のコレクションを返すように変更する場合があります。(そうでなければ、ライブラリメソッド(あなたのものではない)によって返されたnullを見つけている場合、それは残念なことであり、それらをラップして正規化します。)

https://stackoverflow.com/questions/1191919/what-does-linq-return-when-the-results-are-emptyも参照して ください


1
それnullは、賢明な慣習を使用することで要素がなくなるということではありません。ネイティブのOLESTRINGSについて考えてみてください。実際にそれを意味します。
デュプリケータ

1
@ Deduplicator、MicrosoftのObject Linking and Embedding perchanceについて話していますか?OLEが90年代のものであることを思い出してください!現在、さらに多くの最新言語(C#、Java、Swift)がnullの使用を排除/根絶する機能を提供しています。
エリックエイド

2
@ErikEidt彼らはそれをやっています。それは、null「情報を失う」ことを意味しないからです。null意味のある単一の解釈はありません。代わりに、それはあなたがそれをどう扱うかという問題です。null「情報の欠落」、「要素なし」、「エラーの発生」、「初期化されていない」、「競合する情報」、または任意のアドホックな事柄にも使用されます。
デレクエルキンス

-1。これは、抽象的な理論ではなく、開発者が下した決定です。null「データなし」を意味するために絶対に頻繁に使用されます。時々それは理にかなっています。他の時間ではありません。
jpmc26

13

脇からヌル条件付き構文あなたの変数は、これまで残ることはできません、この問題を軽減するための別の技術があります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条件を完全に削除できます。


12

このコードのクライアントとして、たとえば((int [])null).Any()がfalseを返すことを期待するのは間違っていますか?

はい、単にあなたがC#であり、その動作が適切に定義され、文書化されているからです。

独自のライブラリを作成している場合、または異なる例外カルチャで異なる言語を使用している場合は、falseを期待する方が合理的です。

個人的には、falseを返すことはプログラムをより堅牢にするより安全なアプローチであるように感じますが、少なくとも議論の余地はあります。


15
その「堅牢性」はしばしば幻想です。バグやその他の問題のために配列を生成するコードが突然NULLを生成し始めた場合、「堅牢な」コードは問題を隠します。時には適切な動作ですが、安全なデフォルトではありません。
GoatInTheMachine

1
@GoatInTheMachine-それが安全なデフォルトであることに同意しませんが、問題を隠すことはなく、そのような振る舞いは時には望ましくないことは正しいです。私の経験では、問題を隠す(または他のコードの問題をキャッチするために単体テストを信頼する)方が、予期しない例外をスローしてアプリをクラッシュさせるよりも優れています。私は本当に-呼び出しコードがnullを送信するのに十分に壊れている場合、例外が適切に処理される可能性は何ですか?
テラスティン

10
私のコード、他の同僚、またはクライアントのいずれかに関係なく、何かが壊れている場合、コードがすぐに失敗し、人々はそれが不確定な状態で不特定の時間継続するよりもそれを知っていたほうがいいです。これを行うことができることは、もちろん贅沢であり、常に適切ではありませんが、それは私が好むアプローチです。
GoatInTheMachine

1
@GoatInTheMachine-私は多くのことに同意しますが、コレクションは異なる傾向があります。nullを空のコレクションとして扱うことは、非常に一貫性のない、または驚くべき動作ではありません。
テラスティン

8
Webリクエストを介して受信したJSONドキュメントから生成された配列があり、実稼働中にAPIのクライアントの1つが部分的に無効なJSONを送信して配列をNULLとしてデシリアライズするという問題が突然発生したとします。最悪の場合、コードは最初の不正なリクエストが入った瞬間にNULL参照例外をスローしました。これはログに表示され、すぐにクライアントに壊れたリクエストを修正するよう指示します。または、コードは「ああ、配列は空です。やる!」静かに「購入バスケットにアイテムがありません」と数週間データベースに書き込みましたか?
GoatInTheMachine

10

繰り返しnullチェックに悩まされる場合は、独自の 'IsNullOrEmpty()'拡張メソッドを作成して、その名前でStringメソッドをミラーリングし、null-checkと.Any()呼び出しの両方を単一の呼び出しにラップすることができます。

それ以外の場合、あなたの質問のコメントで@ 17の@ 17で言及されている解決策は、「標準」メソッドよりも短く、新しいヌル条件構文に精通している人には合理的に明らかです。

if(myCollection?.Any() == true)

4
?? false代わりに使用することもできます== true。これは、のケースのみを処理するため、より明確(よりクリーン)に思えますがnull、実際にはこれ以上冗長ではありません。さらに==、ブール値のチェックは通常、ギャグ反射を引き起こします。=)
jpmc26

2
@ jpmc26:C#についてあまり知りません。なぜmyCollection?.Any()十分ではないのですか?
エリックドゥミニル

6
@EricDuminil null条件演算子の動作方法により、myCollection?.Any()通常のブール値ではなくnullable-Booleanを効果的に返します(つまり、boolではなくbool?)。boolからの暗黙的な変換はありませんか?この特定の例で必要なブールです(if条件の一部としてテストするため)。そのため、truenull結合演算子(??)と明示的に比較するか、使用する必要があります。
スティーブンランド

@ jpmc26 ?? falseは、myCollection?.Any()== trueの場合、読みにくいです。ギャグ反射に関係なく、== trueは暗黙的にテストしようとしているものです。?falseは追加のノイズを追加するだけで、nullの場合、null == trueが失敗した場合に何が起こるかについて評価する必要があります?.
ライアンリーチ

2
@RyanTheLeach私は==実際に機能するかどうかについてもっと一生懸命に考えなければなりません。ブール値がtrueorにしかなれない場合、ブール値が直観的にどうなるかはすでに知っていfalseます。私もそれについて考える必要はありません。しかしnull、ミックスに投入し、今私はそれ==が正しいかどうかを解決する必要があります。??だけではなく、排除nullに戻ってそれを減らす、ケースをtrueし、falseあなたはすでに、すぐに理解しています、。私のギャグ反射に関しては、私が最初にそれを見るとき、私の本能は、それが通常は役に立たないので、それを削除することです。
jpmc26

8

このコードのクライアントとして、たとえば((int [])null).Any()がfalseを返すことを期待するのは間違っていますか?

期待について疑問に思うなら、あなたは意図について考えなければなりません。

null とは非常に異なるものを意味します Enumerable.Empty<T>

Erik Eidtの回答で述べたようにnull、空のコレクションとの間に意味の違いがあります。

それらがどのように使用されることになっているかを一見しよう。

MicrosoftのアーキテクトKrzysztof CwalinaBrad Abramsによって書かれた書籍「Framework Design Guidelines:Conventions、Idioms、and Patterns for Reusable .NET Libraries、2nd Edition」には、次のベストプラクティスが記載されています。

Xは、コレクションのプロパティまたはコレクションを返すメソッドからnull値を返しません。代わりに、空のコレクションまたは空の配列を返します。

最終的にデータベースからデータを取得するメソッドの呼び出しを検討してください。空の配列を受け取った場合、またはEnumerable.Empty<T>単にサンプルスペースが空の場合、つまり結果が空のセットである場合nullただし、このコンテキストで受信すると、エラー状態を意味します

Dan Wilsonのポテトの例えと同じ考え方で、データが空のセットであってもデータについて質問するのは理にかなっています。しかし、setがない場合あまり意味がありません


1
意味が異なるかどうかは、コンテキストに大きく依存します。多くの場合、手元にあるロジックの目的のために、それらは同等の概念を表します。常にではありません、頻繁に。
jpmc26

1
@ jpmc26:この答えのポイントは、c#のコーディングガイドラインによって意味が異なると言うことだと思います。必要に応じて、nullとemptyが同じ場合でも、常に正しいコードを作成できます。また、場合によっては、nullでも空でも同じことを実行できますが、それは同じことを意味するわけではありません。
クリス

@Chrisそして、私は、言語を設計した人々のコーディングガイドラインを、要件、使用しているライブラリ、および作業を行う他の開発者のコ​​ンテキストでコードを評価するための代替として使用することはできないと言っています。それらが文脈において同等になることは決してないという考えは、空のパイの理想主義です。それらは一般に手元のデータと同等ではないかもしれませんが、あなたが書いている特定の関数で結果を生成することと同等かもしれません。その場合、両方をサポートする必要があります、必ず同じ結果が生成されるようにします。
jpmc26

私はあなたが言っていることに混乱するかもしれませんが、nullとemptyが結果を生成するために同等である場合、別々にisnullを使用しない理由がわかりません。他の状況ではそれらを区別することはできません
クリス

@Chris異なるコンテキスト(スコープに対応することが多い)を参照しています。たとえば関数を考えてみましょう。nullそして、空の場合、関数の戻り値は同じになる可能性がありますが、それnullは呼び出し側のスコープで空と同じことを意味するわけではありません。
jpmc26

3

なぜnullと空が異なるのかを説明する多くの答えがあり、それらが異なる扱いを受けるべきかどうかの両方を説明しようとする十分な意見があります。しかし、あなたは尋ねています:

このコードのクライアントとして、たとえば((int [])null).Any()がfalseを返すことを期待するのは間違っていますか?

それは完全に合理的な期待です。あなたは、現在の行動を主張している他の誰かと同じように正しいです。私は現在の実装哲学に同意しますが、推進要因はコンテキスト外の考慮事項に基づいているだけではありません。

ことを考えるとAny()述語ずに本質的であるCount() > 0あなたは、このスニペットから何を期待し、その後?

List<int> list = null;
if (list.Count > 0) {}

またはジェネリック:

List<int> list = null;
if (list.Foo()) {}

あなたが期待してNullReferenceExceptionいると思います。

  • Any()は拡張メソッドであり、拡張オブジェクトとスムーズに統合する必要があり、例外をスローすることは最も驚くべきことではありません。
  • すべての.NET言語が拡張メソッドをサポートしているわけではありません。
  • あなたはいつでも電話をかけることができますがEnumerable.Any(null)そこで間違いなく期待していArgumentNullExceptionます。これは同じメソッドであり、フレームワークの他のほぼすべてと一致する必要があります。
  • nullオブジェクトへのアクセスはプログラミングエラーであり、フレームワークはマジック値としてnullを強制するべきではありません。そのように使用する場合、それを処理するのはあなたの責任です。
  • それは意見があり、あなたは1つの方法を考え、私は別の方法を考える。フレームワークは、可能な限り非標準化する必要があります。
  • あなたは特別なケースを持っているなら、あなたはでなければならない、一貫した:あなたは、他のすべての拡張メソッドの詳細や、より高度に独断決定を取らなければならない:場合はCount()、その後容易な決断だと思わWhere()ないです。どうMax()?EMPTYリストに対して例外をスローしますが、EMPTYリストに対しても例外をスローするべきではありませんnullか?

ライブラリ設計者がLINQの前に行ったのnullは、有効な値(たとえばString.IsNullOrEmpty())が明示的なメソッドを導入することで、既存の設計哲学整合する必要があった場合です。とは言っても、書くのがかなり簡単な場合でも、2つの方法EmptyIfNull()EmptyOrNull()便利かもしれません。


1

ジムは、ジャガイモをすべての袋に入れることになっています。そうでなければ私は彼を殺すつもりです。

ジムには、5つのジャガイモが入った袋があります。.Any()袋にジャガイモは入っていますか?

「はい」とあなたは言います。<= true

OK、ジムは今回住んでいます。

ジムはジャガイモをすべて取り出して食べます。袋の中に。()ジャガイモがありますか?

「いいえ」とあなたは言います。<= false

ジムを殺す時間。

ジムは火でバッグを完全に焼却します。現在、バッグの中に任意のジャガイモがありますか?

「バッグはありません。」<= ArgumentNullException

ジムは生きるか死ぬべきか?まあ、これは予想していなかったので、裁定が必要です。Jimがこれを回避できるかどうかはバグですか?

あなたはできるのアノテーションを使用しますが、任意のヌル悪ふざけでこの道を入れていないことを知らせるために。

public bool Any( [NotNull] List bag ) 

ただし、ツールチェーンはそれをサポートする必要があります。つまり、最終的に小切手を書くことになります。


0

これが非常に面倒な場合は、簡単な拡張方法をお勧めします。

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ます。


2
書くことはより自然returnのように声明を:return source ?? Enumerable.Empty<T>();
ジャップスティグ・ニールセン

これは尋ねられた質問には答えません。
ケネスK.

@KennethK。しかし、提示された問題を解決するようです。
RQDQ

0

これはC#の拡張メソッドとその設計哲学に関する質問なので、この質問に答える最良の方法は、拡張メソッドの目的に関するMSDNのドキュメントを引用することだと思います

拡張メソッドを使用すると、新しい派生型の作成、再コンパイル、または元の型の変更を行わずに、既存の型にメソッドを「追加」できます。拡張メソッドは特別な種類の静的メソッドですが、拡張タイプのインスタンスメソッドであるかのように呼び出されます。C#、F#、およびVisual Basicで記述されたクライアントコードの場合、拡張メソッドの呼び出しと、実際に型で定義されているメソッドの間に明確な違いはありません。

一般的に、拡張メソッドを控えめに実装することをお勧めします。可能な限り、既存の型を拡張する必要があるクライアントコードは、既存の型から派生した新しい型を作成することで拡張する必要があります。詳細については、継承を参照してください。

拡張メソッドを使用して、ソースコードを変更できない型を拡張する場合、その型の実装の変更により拡張メソッドが破損するリスクがあります。

特定のタイプに拡張メソッドを実装する場合、次の点に注意してください。

  • 型で定義されたメソッドと同じシグネチャを持つ場合、拡張メソッドは呼び出されません。
  • 拡張メソッドは、名前空間レベルでスコープに取り込まれます。たとえば、という名前の単一の名前空間に拡張メソッドを含む複数の静的クラスがある場合Extensions、それらはすべてusing Extensions;ディレクティブによってスコープに取り込まれます。

要約すると、拡張メソッドは、開発者が直接追加できない場合でも、特定の型にインスタンスメソッドを追加するように設計されています。また、インスタンスメソッドは、存在する場合は常に拡張メソッドをオーバーライドするため(インスタンスメソッド構文を使用して呼び出された場合)、メソッドを直接追加したりクラスを拡張したりできない場合にのみ行う必要があります。*

言い換えると、拡張メソッドはインスタンスメソッドと同じように動作する必要があります。これは、クライアントによってインスタンスメソッドにされる可能性があるためです。インスタンスメソッドはnull、呼び出されているオブジェクトがの場合にスローする必要があるため、拡張メソッドもスローする必要があります。


*補足として、これはまさにLINQのデザイナーが直面した状況です。C#3.0がリリースされたとき、コレクションとループの両方でSystem.Collections.IEnumerableとを使用している何百万ものクライアントが既に存在していSystem.Collections.Generic.IEnumerable<T>ましたforeach。これらのクラスは、返されたIEnumeratorだけで2つのメソッドを持っていたオブジェクトCurrentMoveNextそのような任意の追加の必要なインスタンスメソッド、追加、CountAny、など、クライアントのこれらの何百万人を壊すことになるが。だから、(それがの点で実施することができるので、特にこの機能を提供するために、CurrentそしてMoveNext比較的容易に)、それらは任意の現存に適用することができる拡張メソッド、としてそれをリリースIEnumerableまた、より効率的な方法でクラスによって実装することもできます。C#の設計者がLINQを初日にリリースすることを決めた場合、それはのインスタンスメソッドとしてIEnumerable提供され、それらのメソッドの既定のインターフェイス実装を提供する何らかのシステムを設計したでしょう。


0

ここでの顕著な点は、例外をスローする代わりにfalseを返すことで、コードの将来のリーダー/修飾子に関連する可能性のある情報を難読化することだと思います。

リストがnullになる可能性がある場合は、そのために別のロジックパスを用意することをお勧めします。予測する理由がなかったという望ましくない例外。

読みやすさと保守性は、毎回「余分な条件を記述する必要があります」に勝っています。


0

原則として、ほとんどのコードを書いて、主にNullを返さない(および他の同様の投稿)で説明されている理由により、呼び出し元がnullデータを渡さないようにする責任があると想定しています。nullの概念はよく理解されていないことが多く、そのため、実用的な限り変数を事前に初期化することをお勧めします。妥当なAPIを使用していると仮定すると、理想的にはnull値を取得しないようにする必要があるため、nullをチェックする必要はありません。他の答えが述べているように、nullのポイントは、続行する前に作業する有効なオブジェクト(「バッグ」など)があることを確認することです。これはの機能ではなくAny言語の機能です自体。nullオブジェクトは存在しないため、ほとんどの操作はできません。私は車を持っていないことを除いて、私の車で店に運転するように頼むようなものです。したがって、あなたは私の車を使って店に行く方法がありません。文字通り存在しないものに対して操作を実行するのは無意味です。

nullを使用する実用的なケースがあります。たとえば、要求された情報を文字通り入手できない場合などです(たとえば、私の年齢を尋ねた場合、この時点で答える可能性が最も高い選択肢は「わからない」 「これはヌル値です)。ただし、ほとんどの場合、少なくとも変数が初期化されているかどうかを知っている必要があります。そうでない場合は、コードを強化する必要があることをお勧めします。プログラムまたは関数で最初に行うことは、値を使用する前に初期化することです。変数がnullでないことを保証できるため、null値をチェックする必要があることはまれです。取得するのは良い習慣であり、変数を初期化する頻度を高くするほど、nullをチェックする必要が少なくなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.