web-dev-qa-db-fra.com

Pourquoi Enumerable.All renvoie true pour une séquence vide?

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

Le code crée une collection vide de chaîne, puis essaie de déterminer si tous les éléments de la collection sont "ABC". Si vous l'exécutez, b sera vrai.

Mais la collection ne contient même aucun élément, et encore moins des éléments égaux à "ABC".

Est-ce un bug ou existe-t-il une explication raisonnable?

97

Ce n'est certainement pas un bug. Il se comporte exactement comme documenté :

true si chaque élément de la séquence source réussit le test dans le prédicat spécifié, ou si la séquence est vide ; sinon, faux.

Maintenant, vous pouvez vous demander si cela - devrait fonctionner de cette façon (cela me semble bien; chaque élément de la séquence est conforme au prédicat) mais le toute première chose à vérifier avant de demander si quelque chose est un bug, c'est la documentation. (C'est la première chose à vérifier dès qu'une méthode se comporte d'une manière différente de ce que vous attendiez.)

148
Jon Skeet

Tout requiert que le prédicat soit vrai pour tous les éléments de la séquence. Ceci est explicitement indiqué dans la documentation. C'est aussi la seule chose qui ait du sens si vous pensez à Tout comme étant comme une logique et entre les résultats du prédicat pour chaque élément. Le "vrai" que vous sortez pour la séquence vide est l'élément d'identité de l'opération et. De même, le faux que vous obtenez de Any pour la séquence vide est l'identité de logique ou.

Si vous pensez que "tous" est "il n'y a pas d'éléments dans la séquence qui ne le sont pas", cela pourrait avoir plus de sens.

10
Mr. Putty

C'est true, car rien (aucune condition) ne le fait false.

Les docs l'expliquent probablement. (Jon Skeet a également mentionné quelque chose il y a quelques années)

Il en va de même pour Any (l'opposé de All) renvoyant false pour les ensembles vides.

Modifier:

Vous pouvez imaginer que All soit implémenté sémantiquement comme:

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

La plupart des réponses semblent aller dans le sens de "parce que c'est ainsi que l'on définit". Mais il y a aussi une raison logique pour laquelle est défini de cette façon.

Lorsque vous définissez une fonction, vous souhaitez que votre fonction soit aussi générale que possible, de sorte qu'elle puisse être appliquée au plus grand nombre possible de cas. Disons, par exemple, que je veux définir la fonction Sum, qui retourne la somme de tous les nombres dans une liste. Que doit-il renvoyer lorsque la liste est vide? Si vous renvoyiez un nombre arbitraire x, vous définiriez la fonction comme:

  1. Fonction qui renvoie la somme de tous les nombres de la liste donnée, ou x si la liste est vide.

Mais si x est nul, vous pouvez également le définir comme

  1. Fonction qui renvoie x plus les nombres donnés.

Notez que la définition 2 implique la définition 1, mais 1 n'implique pas 2 lorsque x n'est pas zéro, ce qui en soi est une raison suffisante pour choisir 2 sur 1. Mais la note 2 est également plus élégant et, en soi, plus général que 1. C'est comme placer un projecteur plus loin pour éclairer une zone plus grande. Beaucoup plus gros en fait. Je ne suis pas moi-même mathématicien, mais je suis sûr qu'ils trouveront une tonne de liens entre la définition 2 et d'autres concepts mathématiques, mais pas autant liés à la définition 1 lorsque x n'est pas nul.

En général, vous pouvez, et très probablement vouloir retourner le élément d'identité (celui qui laisse l'autre opérande inchangé) chaque fois que vous avez une fonction qui applique un opérateur binaire sur un ensemble d'éléments et l'ensemble est vide. C'est la même raison pour laquelle une fonction Product renverra 1 lorsque la liste est vide (notez que vous pouvez simplement remplacer "x plus" par "une fois" dans la définition 2). Et c'est la même raison que All (qui peut être considérée comme l'application répétée de l'opérateur logique AND) renverra true lorsque la liste est vide (p && true est équivalent à p), et la même raison Any (l'opérateur OR) renverra false.

3
Juan

La méthode parcourt tous les éléments jusqu'à ce qu'elle en trouve un qui ne remplisse pas la condition ou n'en trouve aucun qui échoue. Si aucun échoue, true est retourné.

Donc, s'il n'y a aucun élément, true est retourné (car il n'y en avait aucun qui a échoué)

3
Andrew Barber

Voici une extension qui peut faire ce que OP voulait faire:

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;
}

... et comme d'autres l'ont déjà souligné, ce n'est pas un bug mais un comportement bien documenté.

Une solution alternative si l'on ne souhaite pas écrire une nouvelle extension est:

strs.DefaultIfEmpty().All(str => str == "ABC");

PS: ce qui précède ne fonctionne pas si vous recherchez la valeur par défaut elle-même! (Ce qui pour les chaînes serait nul.) Dans de tels cas, il devient moins élégant avec quelque chose de similaire à:

strs.DefaultIfEmpty(string.Empty).All(str => str == null);

Si vous pouvez énumérer plusieurs fois la solution la plus simple est:

strs.All(predicate) && strs.Any();

c'est-à-dire ajouter simplement une vérification après qu'il y ait effectivement eu l'élément any.

1
AnorZaken

Garder la mise en œuvre de côté. Est-ce vraiment important si c'est vrai? Voyez si vous avez du code qui itère sur l'énumérable et exécute du code. si All () est vrai, ce code ne fonctionnera toujours pas, car l'énumérateur ne contient aucun élément.

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