LINQ: Not Any vs All Don ' t

Często chcę sprawdzić, czy podana wartość pasuje do jednej z list (np. podczas walidacji):

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

Ostatnio zauważyłem, że ReSharper prosi mnie o uproszczenie tych zapytań do:

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

Oczywiście, jest to logicznie identyczne, być może nieco czytelniejsze (jeśli dużo uczyniłeś matematyki), moje pytanie brzmi: czy to skutkuje hitem wydajności?

Wydaje się, że powinien (tzn. .Any() brzmi jak zwarcie, podczas gdy .All() brzmi jak nie), ale nie mam co tego uzasadniać. Czy ktoś ma głębszą wiedzę na temat tego, czy zapytania rozwiążą to samo, czy ReSharper prowadzi mnie na manowce?

Author: Jon Hanna, 2012-01-27

8 answers

Implementacja All według ILSpy (jak w rzeczywistości poszedłem i spojrzałem, a nie " cóż, ta metoda działa trochę jak ..."Mógłbym zrobić, gdybyśmy omawiali teorię, a nie wpływ).

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

Implementacja Any według 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;
}

Oczywiście, może być jakaś subtelna różnica w IL wytwarzanym. Ale nie, nie ma. IL jest prawie taki sam, ale dla oczywistej inwersji zwracania true na predicate match versus returning false przy niedopasowaniu predykatu.

To oczywiście linq-for-objects. Jest możliwe, że jakiś inny dostawca linq traktuje jeden o wiele lepiej niż drugi, ale jeśli tak było, to jest całkiem losowe, który z nich ma bardziej optymalną implementację.

Wydaje się, że reguła sprowadza się wyłącznie do kogoś, kto uważa, że if(determineSomethingTrue) jest prostsza i bardziej czytelna niż if(!determineSomethingFalse). I szczerze mówiąc, myślę, że mają trochę racji w tym, że często znajduję if(!someTest) mylące*, gdy jest alternatywny test o jednakowej szczegółowości i złożoności, który zwróci wartość true dla warunku, na którym chcemy działać. Jednak naprawdę, ja osobiście nie znajduję nic, aby faworyzować jedną z dwóch alternatyw, które podajesz, i być może pochyliłbym się bardzo nieznacznie w kierunku pierwszej, gdyby orzeczenie było bardziej skomplikowane.

*nie mylące jak w nie rozumiem, ale mylące jak w martwię się, że jest jakiś subtelny powód do decyzji, że nie Rozumiem, i to wymaga kilku umysłowych przeskoków, aby zdaj sobie sprawę, że "nie, po prostu zdecydowali się zrobić to w ten sposób, czekaj, po co znowu patrzyłem na ten kawałek kodu?..."

 350
Author: Jon Hanna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-27 00:52:21

Te metody rozszerzenia mogą sprawić, że Twój kod będzie bardziej czytelny:

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

Teraz zamiast oryginału

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

Można powiedzieć

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
 56
Author: AakashM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-27 09:10:19

Oba mają identyczną wydajność, ponieważ oba kończą wyliczanie po tym, jak można określić wynik - Any() na pierwszej pozycji przekazany predykat ocenia na true i All() na pierwszej pozycji predykat ocenia na false.

 29
Author: BrokenGlass,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-27 00:32:58

All zwarcie przy pierwszym braku dopasowania, więc to nie problem.

Jednym z obszarów subtelności jest to, że

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 
To prawda. Wszystkie pozycje w sekwencji są parzyste.

Więcej informacji na temat tej metody można znaleźć w dokumentacji Enumerable.Wszystkie .

 21
Author: Anthony Pegram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-27 00:32:37

All() określa, czy wszystkie elementy ciągu spełniają warunek.
Any() określa, czy dowolny element ciągu spełnia warunek.

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

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true
 8
Author: emy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-05 03:53:53

Zgodnie z tym linkiem

Any-sprawdza co najmniej jeden mecz

Wszystkie-sprawdza, czy wszystkie pasują

 7
Author: rcarvalhoxavier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-26 18:08:29

Inne odpowiedzi zostały dobrze omówione: tu nie chodzi o wydajność, chodzi o jasność.

Istnieje szerokie wsparcie dla obu opcji:

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

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

Ale myślę, że to może osiągnąć szersze wsparcie :

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

Po prostu obliczenie logiki (i nazwanie jej) przed zanegowaniem czegokolwiek oczyszcza to w moim umyśle.

 7
Author: Michael Haren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-06 14:00:29

Jeśli spojrzysz na Enumerable source zobaczysz, że implementacja Any i All jest dość blisko:

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

Nie ma możliwości, aby jedna metoda była znacznie szybsza od drugiej, ponieważ jedyna różnica polega na negacji logicznej, więc wolę czytelność niż fałszywą wydajność.

 4
Author: Thomas Ayoub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-22 08:54:06