web-dev-qa-db-ja.com

Enumerable.Allが空のシーケンスに対してtrueを返すのはなぜですか?

var strs = new Collection<string>();
bool b = strs.All(str => str == "ABC");

コードは空の文字列のコレクションを作成し、コレクション内のすべての要素が「ABC」であるかどうかを判断しようとします。実行すると、bはtrueになります。

ただし、コレクションには要素も含まれておらず、「ABC」に等しい要素はありません。

これはバグですか、それとも合理的な説明がありますか?

97

確かにバグではありません。それは正確に動作します 文書化されているように

ソースシーケンスのすべての要素が指定された述語でテストに合格した場合、またはシーケンスが空の場合;それ以外の場合はfalse。

今、あなたはそれがshouldそのように動作するかどうかについて議論することができます(私にとってはうまくいくようです;シーケンスのすべての要素は述語に準拠しています)が、最初のもの何かがバグかどうかを尋ねる前にcheckするのがドキュメントです。 (メソッドが予期した以外の方法で動作するとすぐにチェックする最初のことです。)

148
Jon Skeet

すべてでは、シーケンスのすべての要素について述語が真である必要があります。これは、ドキュメントに明示的に記載されています。また、Allを論理的であり、各要素の述語の結果の間であると考える場合に意味をなす唯一のものです。空のシーケンスに対して取得している「true」は、and操作のアイデンティティ要素です。同様に、空のシーケンスに対してAnyから取得するfalseは、論理ORのIDです。

「すべて」を「シーケンス内に存在しない要素がない」と考える場合、これはより理にかなっているかもしれません。

10
Mr. Putty

trueになります(条件なし)ので、falseになります。

ドキュメントはおそらくそれを説明しています。 (ジョン・スキートは数年前に何かを述べました)

AnyAllの反対)についても同様です。空のセットに対してfalseを返します。

編集:

Allが意味的に同じように実装されると想像できます:

foreach (var e in elems)
{
  if (!cond(e))
    return false;
}
return true; // no escape from loop
8
leppie

このメソッドは、条件を満たさない要素が見つかるまで、または失敗する要素が見つからないまで、すべての要素を循環します。何も失敗しない場合、trueが返されます。

したがって、要素がない場合は、trueが返されます(失敗したものはなかったため)

3
Andrew Barber

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();

つまり、実際にany要素があった後にチェックを追加するだけです。

1
AnorZaken

実装を脇に置きます。それが本当かどうかは本当に重要ですか? enumerableを反復処理してコードを実行するコードがあるかどうかを確認します。 All()がtrueの場合、enumerableには要素が含まれていないため、そのコードは実行されません。

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.
1