web-dev-qa-db-fra.com

LINQ: Pas tous contre tous ne pas

Souvent, je veux vérifier si une valeur fournie correspond à une valeur 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 la suivante: cela a-t-il un impact négatif sur les performances?

Cela donne l’impression que cela devrait (c’est-à-dire .Any() sonne comme un court-circuit, alors que .All() sonne comme si ce n’était pas le cas), mais je n’ai rien à dire à ce sujet. Est-ce que quelqu'un a une connaissance plus approfondie de la question de savoir si les requêtes vont résoudre le même problème ou si ReSharper m'égare?

257
Mark

Implémentation de All selon ILSpy (comme dans le cas, j’ai regardé plutôt que le "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 Any selon 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 légère différence dans l’IL produite. Mais non, non il n'y en a pas. L'IL est à peu près la même chose, à l'exception de l'inversion évidente entre renvoyer vrai sur correspondance de prédicat et renvoyer faux sur incompatibilité de prédicat.

Ceci est bien sûr linq-for-objects. Il est possible qu'un autre fournisseur linq traite l'un bien mieux que l'autre, mais dans ce cas, le choix de l'implémentation optimale est plutôt aléatoire.

Il semblerait que la règle découle uniquement du sentiment que if(determineSomethingTrue) est plus simple et lisible que if(!determineSomethingFalse). Et par souci d'équité, je pense qu'ils ont un point en ce sens que je trouve souvent que if(!someTest) est déroutant * lorsqu'il existe un test alternatif d'égale verbosité et de complexité qui reviendrait à la réalité pour la condition sur laquelle nous voulons agir. Pourtant, en réalité, je ne trouve personnellement rien qui puisse privilégier l’une des deux alternatives que vous proposez, et je pencherais peut-être très légèrement vers la première si le prédicat était plus compliqué.

* Pas déroutant en tant que je ne comprends pas, mais confus en tant que je crains qu'il y ait une raison subtile à la décision que je ne comprends pas, et il faut quelques sautes de tête pour se rendre compte que "non, ils ont juste décidé de le faire attends, qu'est-ce que je cherchais à nouveau dans ce code? ... "

326
Jon Hanna

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
}

vous pourriez dire

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
52
AakashM

Les deux performances sont identiques car elles arrêtent l'énumération après le résultat - Any() sur le premier élément du prédicat transmis est évalué à true et All() sur le premier élément auquel le prédicat est évalué false.

23
BrokenGlass

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 de Enumerable.All .

19
Anthony Pegram

All() détermine si tous les éléments d'une séquence remplissent 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
6
emy

Comme d'autres réponses l'ont bien dit: il ne s'agit pas de performance, mais 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 atteindre 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 de nier quoi que ce soit clarifie cela beaucoup dans mon esprit.

5
Michael Haren

Selon cela lien

Any - Vérifie au moins une correspondance

Tous - Vérifie que tous correspondent

5
rcarvalhoxavier

Si vous regardez la Enumerable source vous verrez que l'implémentation de Any et All est 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 est impossible qu'une méthode soit significativement plus rapide que l'autre, car la seule différence réside dans une négation booléenne. Préférez donc la lisibilité à la fausse performance.

3
Thomas Ayoub