var strs = new Collection<string>();
bool b = strs.All(str => str == "ABC");
このコードは、文字列の空のコレクションを作成し、コレクション内のすべての要素が "ABC" であるかどうかを判断しようとします。実行すると、b
trueになります。
しかし、コレクションには、「ABC」に等しい要素は言うまでもなく、その中に要素さえありません。
これはバグですか、それとも合理的な説明はありますか?
var strs = new Collection<string>();
bool b = strs.All(str => str == "ABC");
このコードは、文字列の空のコレクションを作成し、コレクション内のすべての要素が "ABC" であるかどうかを判断しようとします。実行すると、b
trueになります。
しかし、コレクションには、「ABC」に等しい要素は言うまでもなく、その中に要素さえありません。
これはバグですか、それとも合理的な説明はありますか?
それは確かにバグではありません。文書化されているとおりに動作しています:
ソース シーケンスのすべての要素が指定された述語のテストに合格する場合、またはシーケンスが空の場合はtrue 。それ以外の場合は false。
これで、そのように動作するかどうかについて議論することができます(私には問題ないように思えます。シーケンスのすべての要素が述語に準拠しています)。しかし、何かがバグであるかどうかを尋ねる前に、最初に確認する必要があるのはドキュメントです。(メソッドが予想とは異なる動作をした場合、最初に確認することです。)
All
シーケンスのすべての要素に対して述語が true である必要があります。これは、ドキュメントに明示的に記載されています。またAll
、各要素の述語の結果の間の論理「and」のようなものと考えると、これが唯一の意味を持ちます。空のtrue
シーケンスを取得しているのは、「and」操作のアイデンティティ要素です。同様に、空のシーケンスfalse
から得Any
られる は、論理的な「または」のアイデンティティです。
All
「シーケンスにそうでない要素はない」と考えれば、これはより理にかなっているかもしれません。
それはtrue
、何も(無条件)がそれを作るのでfalse
です。
ドキュメントはおそらくそれを説明しています。(Jon Skeet も数年前に言及しました)
空のセットを返す場合も同様ですAny
(の反対)。All
false
編集:
All
次のように意味的に実装されると想像できます。
foreach (var e in elems)
{
if (!cond(e))
return false;
}
return true; // no escape from loop
ここでのほとんどの回答は、「そのように定義されているため」の行に沿っているようです。しかし、 がこのように定義されているのには論理的な理由もあります。
関数を定義するときは、可能な限り多くのケースに適用できるように、関数をできるだけ一般的なものにする必要があります。たとえば、Sum
リスト内のすべての数値の合計を返す関数を定義したいとします。リストが空の場合、何を返す必要がありますか? 任意の数値x
を返す場合は、関数を次のように定義します。
x
リストが空の場合。しかし、x
がゼロの場合は、次のように定義することもできます
x
指定された数値に加えて返す関数。定義 2 は定義 1 を意味しますが、1 がx
ゼロでない場合、1 は 2 を意味しないことに注意してください。これだけでも、1 よりも 2 を選択する十分な理由になります。しかし、2 はよりエレガントであり、それ自体が 1 よりも一般的であることに注意してください。スポットライトを遠くに置いて、広い範囲を明るくするようなものです。実際にはもっと大きい。私自身は数学者ではありませんが、彼らは定義 2 と他の数学的概念との間に多くの関連性を見つけるだろうと確信していますが、定義 1 に関連するものはそれほど多くx
はありません。
一般に、要素のセットに二項演算子を適用する関数があり、セットが空の場合はいつでも恒等要素(他のオペランドを変更しないままにするもの)を返すことができます。これは、リストが空のときに関数が 1 を返すのと同じ理由です (定義 2 で「プラス」を「1 回」にProduct
置き換えることができることに注意してください)。x
And は同じ理由All
(論理 AND 演算子の繰り返し適用と考えることができます) がtrue
、リストが空の場合 (p && true
は と同等) を返しp
、同じ理由Any
(OR 演算子) が を返しfalse
ます。
このメソッドは、条件を満たさない要素が見つかるか、条件を満たさない要素が見つからなくなるまで、すべての要素を循環します。失敗しない場合は、true が返されます。
したがって、要素がない場合は true が返されます (失敗した要素がないため)
OPがやりたかったことを実行できる拡張機能を次に示します。
static bool All<T>(this IEnumerable<T> source, Func<T, bool> predicate, bool mustExist)
{
foreach (var e in source)
{
if (!predicate(e))
return false;
mustExist = false;
}
return !mustExist;
}
...そして、他の人がすでに指摘しているように、これはバグではなく、十分に文書化された意図された動作です。
新しい拡張機能を書きたくない場合の代替ソリューションは次のとおりです。
strs.DefaultIfEmpty().All(str => str == "ABC");
PS: デフォルト値自体を探している場合、上記は機能しません! (文字列の場合はnullになります。)そのような場合、次のようなものではエレガントではなくなります。
strs.DefaultIfEmpty(string.Empty).All(str => str == null);
複数回列挙できる場合、最も簡単な解決策は次のとおりです。
strs.All(predicate) && strs.Any();
つまり、実際に要素があった後にチェックを追加するだけです。
実装は脇に置いておきます。それが本当かどうかは本当に重要ですか?列挙型を反復処理してコードを実行するコードがあるかどうかを確認してください。All() が true の場合、列挙型に要素が含まれていないため、そのコードはまだ実行されません。
var hungryDogs = Enumerable.Empty<Dog>();
bool allAreHungry = hungryDogs.All(d=>d.Hungry);
if (allAreHungry)
foreach (Dog dog in hungryDogs)
dog.Feed(biscuits); <--- this line will not run anyway.