LINQ: Nie każdy kontra wszyscy nie

272

Często chcę sprawdzić, czy podana wartość pasuje do wartości na liście (np. Podczas sprawdzania poprawności):

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

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

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

Oczywiście jest to logicznie identyczne, być może nieco bardziej czytelne (jeśli zrobiłeś dużo matematyki), moje pytanie brzmi: czy to prowadzi do wydajności?

Wydaje się, że powinien (tzn. .Any()Brzmi jak zwiera, podczas gdy .All()brzmi tak, jakby nie), ale nie mam na to dowodów. Czy ktoś ma głębszą wiedzę, czy zapytania rozwiążą to samo, czy też ReSharper prowadzi mnie na manowce?

znak
źródło
6
Czy próbowałeś zdemontować kod Linq, aby zobaczyć, co robi?
RQDQ
9
W tym przypadku faktycznie bym poszedł z if (! AcceptValues.Contains (someValue)), ale oczywiście nie o to chodziło :)
csgero
2
@csgero Zgadzam się. Powyższe stanowiło uproszczenie (być może nadmierne uproszczenie) prawdziwej logiki.
Mark
1
„Wydaje się, że powinien (tzn. Każdy () brzmi, jakby zwarcie, podczas gdy .All () brzmi, jakby nie)” - nie dla każdego, kto ma intuicję dźwiękową. Zauważ, że logiczna równoważność, którą zauważysz, oznacza, że ​​mogą one być równie zwarte. Chwila zastanowienia ujawnia, że ​​Wszyscy mogą odejść, gdy tylko napotkamy przypadek niekwalifikujący się.
Jim Balter
3
Nie zgadzam się w tej sprawie z ReSharper. Napisz rozsądne ciągi myśli. Jeśli chcesz rzucić wyjątek, jeśli wymagana pozycja brakuje: if (!sequence.Any(v => v == true)). Jeśli chcesz kontynuować, czy wszystko jest zgodny z określoną specyfikacją: if (sequence.All(v => v < 10)).
Timo,

Odpowiedzi:

344

Wdrożenie Allzgodnie z ILSpy (jak w rzeczywistości poszedłem i spojrzałem, zamiast „dobrze, ta metoda działa trochę jak ...” Mogę to zrobić, jeśli omawiamy 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 Anyzgodnie z 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 występować niewielka różnica w wytwarzanej IL. Ale nie, nie, nie ma. IL jest prawie taka sama, ale dla oczywistej inwersji zwracania wartości true w przypadku dopasowania predykatu w porównaniu do zwracania wartości false w przypadku niedopasowania predykatu.

Oczywiście jest to tylko linq dla obiektów. Możliwe, że jakiś inny dostawca linq traktuje jednego znacznie lepiej od drugiego, ale jeśli tak było, to raczej losowe, który otrzymał bardziej optymalną implementację.

Wydaje się, że reguła sprowadza się wyłącznie do tego, że ktoś czuje, że if(determineSomethingTrue)jest prostszy i bardziej czytelny niż if(!determineSomethingFalse). I szczerze mówiąc, myślę, że mają one trochę racji w tym, że często mylę się if(!someTest)*, gdy istnieje alternatywny test o równej gadatliwości i złożoności, który zwróciłby prawdę w przypadku warunku, na którym chcemy działać. Ale tak naprawdę, osobiście nie znajduję nic, co by sprzyjało jednej z dwóch alternatyw, które dajesz, i być może pochyliłbym się bardzo nieznacznie do pierwszej, gdyby orzeczenie było bardziej skomplikowane.

* Nie mylące jak w nie rozumiem, ale mylące jak w martwię się, że istnieje jakiś subtelny powód decyzji, której nie rozumiem, i kilka mentalnych przeskoków zdaje sobie sprawę, że „nie, po prostu postanowili to zrobić w ten sposób, poczekaj, po co znów patrzyłem na ten fragment kodu? ... "

Jon Hanna
źródło
8
Nie jestem pewien, co się dzieje za liniami, ale dla mnie jest o wiele bardziej czytelny: jeśli (nie ma) niż jeśli (wszystko nie jest równe).
VikciaR,
49
Istnieje DUŻA różnica, gdy twoje wyliczenie nie ma wartości. „Dowolny” zawsze zwraca FALSE, a „Wszystko” zawsze PRAWDA. Zatem powiedzenie, że jeden jest logicznym odpowiednikiem drugiego, nie jest do końca prawdą!
Arnaud
44
@ Arnaud Anypowróci, falsea zatem !Anypowróci true, więc są identyczne.
Jon Hanna
11
@Arnaud Nie ma nikogo, kto skomentowałby, że Any i All są logicznie równoważne. Innymi słowy, wszystkie osoby, które wypowiedziały się, nie powiedziały, że Any and All są logicznie równoważne. Równoważność występuje między! Any (predykat) i All (! Predicate).
Jim Balter,
7
@MacsDickinson wcale nie jest to różnica, ponieważ nie porównujesz przeciwnych predykatów. Odpowiednikiem !test.Any(x => x.Key == 3 && x.Value == 1)tych zastosowań Alljest test.All(x => !(x.Key == 3 && x.Value == 1))(co w istocie jest równoważne test.All(x => x.Key != 3 || x.Value != 1)).
Jon Hanna
55

Może się okazać, że te metody rozszerzeń czynią kod bardziej czytelnym:

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 twojego oryginału

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

można powiedzieć

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
AakashM
źródło
6
Dzięki - już myślałem o wdrożeniu ich w naszej wspólnej bibliotece, ale jeszcze nie zdecydowałem, czy to dobry pomysł. Zgadzam się, że sprawiają, że kod jest bardziej czytelny, ale obawiam się, że nie dodają wystarczającej wartości.
Mark
2
Szukałem None i nie znalazłem. Jest o wiele bardziej czytelny.
Rhyous,
Musiałem dodać kontrole zerowe: return source == null || ! source.Any (predicate);
Rhyous,
27

Oba miałyby identyczną wydajność, ponieważ oba zatrzymują wyliczanie po wyniku - Any()na pierwszym elemencie przekazywany predykat ocenia truei All()na pierwszym elemencie, na którym ocenia predykat false.

Rozbite szkło
źródło
21

All zwarcia przy pierwszym braku dopasowania, więc nie stanowi to problemu.

Jednym z obszarów subtelności jest to

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

Jest prawdziwy. Wszystkie elementy w sekwencji są parzyste.

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

Anthony Pegram
źródło
12
Tak, ale bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)to też prawda.
Jon Hanna
1
@Jon semantycznie brak! = Wszystkie. Tak więc semantycznie albo nie masz żadnych, albo wszystkie, ale w przypadku .All () none jest tylko podzbiorem wszystkich kolekcji, które zwracają wartość true dla wszystkich, a ta rozbieżność może powodować błędy, jeśli nie jesteś tego świadomy. +1 za tego Anthony'ego
Rune FS
@RuneFS Nie śledzę. Semantycznie i logicznie „nikt tam, gdzie nie jest prawdą, że ...” jest w istocie tym samym, co „wszystko tam, gdzie to prawda”. Np. „Gdzie żaden z zaakceptowanych projektów z naszej firmy?” zawsze będzie miał tę samą odpowiedź, co „gdzie wszystkie zaakceptowane projekty innych firm?” ...
Jon Hanna
... To prawda, że ​​możesz mieć błędy, zakładając, że „wszystkie elementy są…” oznacza, że ​​istnieje co najmniej jeden element, który jest co najmniej jednym elementem, który spełnia test, ponieważ „wszystkie elementy ... „jest zawsze prawdziwe w przypadku pustego zestawu, w ogóle tego nie kwestionuję. Dodałem jednak, że ten sam problem może się zdarzyć przy założeniu, że „żaden z elementów ...” oznacza, że ​​co najmniej jeden element nie spełnia testu, ponieważ „żaden z elementów ...” jest również zawsze prawdziwy dla pustego zestawu . Nie chodzi o to, że nie zgadzam się z argumentem Anthony'ego, że myślę, że dotyczy to również dwóch omawianych konstrukcji.
Jon Hanna
@Jon mówisz logiką, a ja mówię o językoznawstwie. Ludzki mózg nie może przetworzyć ujemnego (zanim przetworzy dodatni, w którym momencie może go następnie zanegować), więc w tym sensie istnieje między nimi dość duża różnica. To nie czyni logiki, którą proponujesz niepoprawną
Rune FS
8

All()określa, czy wszystkie elementy sekwencji spełniają warunek.
Any()określa, czy jakikolwiek element sekwencji 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
emy
źródło
7

Według tego linku

Dowolny - sprawdza co najmniej jeden mecz

Wszystkie - sprawdza, czy wszystkie pasują

rcarvalhoxavier
źródło
1
masz rację, ale zatrzymują się w tym samym czasie dla danej kolekcji. Wszystkie przerwy, gdy warunek się nie powiedzie, i Wszelkie przerwy, gdy pasuje do predykatu. Technicznie nie inaczej, z wyjątkiem scenicznego
WPFKK
6

Jak dobrze ujęto inne odpowiedzi: nie chodzi o wydajność, chodzi o przejrzystość.

Istnieje szeroka obsługa obu opcji:

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

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

Myślę jednak, że może to zapewnić szersze wsparcie :

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

Po prostu obliczenie wartości logicznej (i nadanie jej nazwy) przed negowaniem czegokolwiek bardzo to wyjaśnia.

Michael Haren
źródło
3

Jeśli spojrzysz na źródło Enumerable , zobaczysz, że implementacja Anyi Alljest 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 boolowskiej negacji, więc wolę czytelność niż fałszywą wydajność.

Thomas Ayoub
źródło