LINQ: Pas tout contre tous

272

Souvent, je veux vérifier si une valeur fournie correspond à une dans une liste (par exemple lors de la validation):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

Récemment, j'ai remarqué que ReSharper me demandait de simplifier ces requêtes pour:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Évidemment, c'est logiquement identique, peut-être un peu plus lisible (si vous avez fait beaucoup de mathématiques), ma question est: cela entraîne-t-il un impact sur les performances?

On dirait que ça devrait (c'est-à-dire .Any()que ça sonne comme des courts-circuits, alors .All()que ça ne sonne pas), mais je n'ai rien pour le prouver. Quelqu'un a-t-il une connaissance plus approfondie de savoir si les requêtes vont résoudre le même problème, ou si ReSharper me induit en erreur?

marque
la source
6
Avez-vous essayé de démonter le code Linq pour voir ce qu'il fait?
RQDQ
9
Dans ce cas, j'irais en fait avec (! AcceptedValues.Contains (someValue)), mais bien sûr, ce n'était pas la question :)
csgero
2
@csgero, je suis d'accord. Ce qui précède était une simplification (peut-être une simplification excessive) de la logique réelle.
Mark
1
"On dirait qu'il devrait (c'est-à-dire .Tout () sonne comme des courts-circuits, tandis que .Tout () sonne comme il ne le fait pas)" - Pas pour quiconque a des intuitions sonores. Les équivalences logiques que vous notez impliquent qu'elles sont également court-circuitées. Un instant de réflexion révèle que tout le monde peut quitter dès qu'un cas non éligible est rencontré.
Jim Balter
3
Je ne suis pas universellement d'accord avec ReSharper à ce sujet. Écrivez des pensées raisonnables. Si vous voulez lancer une exception si un élément requis est manquant: if (!sequence.Any(v => v == true)). Si vous souhaitez continuer que si tout est conforme à une certaine spécification: if (sequence.All(v => v < 10)).
Timo

Réponses:

344

Implémentation de Allselon ILSpy (comme en fait je suis allé voir, plutôt que "bien, cette méthode fonctionne un peu comme ..." Je pourrais le faire si nous discutions de la théorie plutôt que de l'impact).

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (!predicate(current))
        {
            return false;
        }
    }
    return true;
}

Implémentation de Anyselon ILSpy:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (predicate(current))
        {
            return true;
        }
    }
    return false;
}

Bien sûr, il pourrait y avoir une différence subtile dans l'IL produit. Mais non, non. L'IL est à peu près le même, mais pour l'inversion évidente du retour de true sur la correspondance de prédicat contre le retour de false sur la non-correspondance de prédicat.

Il s'agit bien sûr de linq-for-objects. Il est possible qu'un autre fournisseur linq en traite un bien mieux que l'autre, mais si tel était le cas, il est à peu près aléatoire lequel a obtenu la mise en œuvre la plus optimale.

Il semblerait que la règle se résume uniquement à quelqu'un qui if(determineSomethingTrue)se sent plus simple et plus lisible que if(!determineSomethingFalse). Et en toute honnêteté, je pense qu'ils ont un petit point en ce que je trouve souvent if(!someTest)déroutant * quand il existe un autre test de verbosité et de complexité égales qui reviendrait vrai pour la condition sur laquelle nous voulons agir. Pourtant, vraiment, je ne trouve personnellement rien à privilégier l'une par rapport à l'autre des deux alternatives que vous donnez, et je pencherais peut-être très légèrement vers la première si le prédicat était plus compliqué.

* Pas déroutant comme dans Je ne comprends pas, mais déroutant comme dans Je m'inquiète qu'il y ait une raison subtile pour la décision que je ne comprends pas, et il faut quelques sauts mentaux pour réaliser que "non, ils ont juste décidé de faire de cette façon, attendez à quoi je cherchais encore ce bout de code? ... "

Jon Hanna
la source
8
Je ne suis pas sûr de ce qui se fait derrière les lignes, mais pour moi, ce qui est beaucoup plus lisible est: si (pas n'importe lequel) que si (tout n'est pas égal).
VikciaR
49
Il y a une GRANDE différence lorsque votre énumération n'a pas de valeurs. 'Any' retournerait toujours FALSE et 'All' retournerait toujours TRUE. Donc, dire que l'un est l'équivalent logique de l'autre n'est pas entièrement vrai!
Arnaud
44
@Arnaud Anyretournera falseet donc !Anysera de retour true, ils sont donc identiques.
Jon Hanna
11
@Arnaud Il n'y a personne qui a commenté que Any et All sont logiquement équivalents. Ou, pour le dire autrement, toutes les personnes qui ont commenté n'ont pas dit que Any and All sont logiquement équivalents. L'équivalence se situe entre! Any (prédicat) et All (! Predicate).
Jim Balter
7
@MacsDickinson ce n'est pas du tout une différence, car vous ne comparez pas des prédicats opposés. L'équivalent de !test.Any(x => x.Key == 3 && x.Value == 1)cette utilisation Allest test.All(x => !(x.Key == 3 && x.Value == 1))(qui est en effet équivalent à test.All(x => x.Key != 3 || x.Value != 1)).
Jon Hanna
55

Vous trouverez peut-être que ces méthodes d'extension rendent votre code plus lisible:

public static bool None<TSource>(this IEnumerable<TSource> source)
{
    return !source.Any();
}

public static bool None<TSource>(this IEnumerable<TSource> source, 
                                 Func<TSource, bool> predicate)
{
    return !source.Any(predicate);
}

Maintenant, au lieu de votre original

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

Tu pourrais dire

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
AakashM
la source
6
Merci - je pensais déjà à les implémenter dans notre bibliothèque commune, mais je n'ai pas encore décidé si c'était une bonne idée. Je conviens qu'ils rendent le code plus lisible, mais je crains qu'ils n'ajoutent pas une valeur suffisante.
Mark
2
J'ai cherché Aucun et je ne l'ai pas trouvé. C'est beaucoup plus lisible.
Rhyous
J'ai dû ajouter des contrôles nuls: return source == null || ! source.Any (prédicat);
Rhyous
27

Les deux auraient des performances identiques, car les deux arrêtent l'énumération une fois le résultat déterminé - Any()sur le premier élément, le prédicat passé est évalué trueet All()sur le premier élément, le prédicat est évalué false.

Verre brisé
la source
21

All courts-circuits sur le premier non-match, donc ce n'est pas un problème.

Un domaine de subtilité est que

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

Est vrai. Tous les éléments de la séquence sont pairs.

Pour plus d'informations sur cette méthode, consultez la documentation d' Enumerable.All .

Anthony Pegram
la source
12
Oui, mais bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)c'est vrai aussi.
Jon Hanna
1
@Jon sémantiquement aucun! = Tout. Donc, sémantiquement, vous n'en avez aucun ou tous, mais dans le cas de .All () aucun n'est qu'un sous-ensemble de toutes les collections qui renvoient true pour tous et cette différence peut entraîner des bogues si vous ne le connaissez pas. +1 pour cet Anthony
Rune FS
@RuneFS je ne suis pas. Sémantiquement et logiquement "aucun où il est faux que ..." est en effet le même que "tout où il est vrai que". Par exemple "où aucun des projets acceptés de notre entreprise?" aura toujours la même réponse que "où tous les projets acceptés d'autres entreprises?" ...
Jon Hanna
... Maintenant, il est vrai que vous pouvez avoir des bogues en supposant que "tous les éléments sont ..." signifie qu'il y a au moins un élément qui est au moins un élément qui remplit le test, puisque le "tous les éléments ... "est toujours vrai pour l'ensemble vide, je ne le conteste pas du tout. J'ai ajouté cependant que le même problème peut se produire en supposant que "aucun des éléments ..." signifie qu'au moins un élément ne remplit pas le test, car "aucun des éléments ..." est également toujours vrai pour l'ensemble vide . Ce n'est pas que je suis en désaccord avec le point d'Anthony, c'est que je pense qu'il vaut également pour l'autre des deux concepts en discussion.
Jon Hanna
@Jon vous parlez de logique et je parle de linguistique. Le cerveau humain ne peut pas traiter un négatif (avant de traiter le positif, à quel point il peut ensuite le nier), donc dans ce sens, il y a une grande différence entre les deux. Cela ne rend pas la logique que vous proposez incorrecte
Rune FS
8

All()détermine si tous les éléments d'une séquence satisfont une condition.
Any()détermine si un élément d'une séquence satisfait à la condition.

var numbers = new[]{1,2,3};

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true
emy
la source
7

Selon ce lien

Tout - Vérifie au moins une correspondance

Tous - Vérifie que tous correspondent

rcarvalhoxavier
la source
1
vous avez raison mais ils s'arrêtent en même temps pour une collection donnée. Toutes les ruptures lorsque la condition échoue et Toutes les ruptures lorsqu'elle correspond à votre prédicat. Donc techniquement pas différent sauf scéniquement
WPFKK
6

Comme d'autres réponses l'ont bien couvert: ce n'est pas une question de performance, c'est une question de clarté.

Il existe un large soutien pour vos deux options:

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Mais je pense que cela pourrait obtenir un soutien plus large :

var isValueAccepted = acceptedValues.Any(v => v == someValue);
if (!isValueAccepted)
{
    // exception logic
}

Le simple fait de calculer le booléen (et de le nommer) avant d'annuler quoi que ce soit clarifie beaucoup cela dans mon esprit.

Michael Haren
la source
3

Si vous jetez un œil à la source Enumerable, vous verrez que l'implémentation de Anyet Allest assez proche:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (predicate(element)) return true;
    }
    return false;
}

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (!predicate(element)) return false;
    }
    return true;
}

Il n'y a aucun moyen qu'une méthode soit significativement plus rapide que l'autre car la seule différence réside dans une négation booléenne, donc préférez la lisibilité à une fausse performance.

Thomas Ayoub
la source