2012-01-27 14 views
217

Często Chcę sprawdzić, czy pod warunkiem wartość odpowiada jeden na liście (np podczas walidacji):LINQ: Nie Wszelkie vs All Nie

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

Ostatnio zauważyłem ReSharper pytając mnie do uproszczenia te zapytania do:

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

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

Wydaje się, że powinno (to jest .Any() brzmi jak zwarcie, podczas gdy .All() brzmi jak nie), ale nie mam nic, co by to uzasadniało. Czy ktoś ma większą wiedzę na temat tego, czy zapytania rozwiążą ten sam problem, czy też ReSharper prowadzi mnie na manowce?

+6

Czy próbowałeś rozmontować kod Linq, aby zobaczyć, co robi? – RQDQ

+9

W tym przypadku chciałbym iść z if (! AcceptedValues.Contains (someValue)), ale oczywiście to nie było pytanie :) – csgero

+2

@csgero Zgadzam się. Powyższe było uproszczeniem (być może nadmiernym uproszczeniem) prawdziwej logiki. – Mark

Odpowiedz

280

Implementacja All według ILSpy (tak jak w rzeczywistości poszedłem i spojrzałem, zamiast "dobrze, ta metoda działa trochę jak ..." Mogłabym zrobić, gdybyśmy dyskutowali raczej o teorii niż o wpływie).

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

Wykonanie 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, mogą pojawić się pewne nieznaczne różnice w IL produkowane. Ale nie, nie, nie ma. IL jest prawie taki sam, ale dla oczywistej inwersji zwracanej wartości true na dopasowaniu predykatów i zwracania wartości false w przypadku niezgodności predykatów.

Oczywiście tylko dla obiektów typu "linq-for-objects". Jest możliwe, że jakiś inny dostawca linq traktuje o wiele lepiej niż inny, ale jeśli tak było, to jest to dość losowe, które ma bardziej optymalną implementację.

Wydaje się, że reguła ta sprowadza się wyłącznie do kogoś, kto czuje, że if(determineSomethingTrue) jest prostszy i bardziej czytelny niż if(!determineSomethingFalse). I w uczciwości, myślę, że mają one pewien punkt w tym, że często uważam, że jest to mylące *, gdy istnieje alternatywny test o równej szczegółowości i złożoności, który powróciłby do stanu, w którym chcemy działać. Ale tak naprawdę, ja osobiście nie znajduję niczego, co mogłoby faworyzować jedną z dwóch oferowanych przez ciebie alternatyw, i być może pochyliłbym się nieznacznie w kierunku pierwszego, gdyby orzeczenie było bardziej skomplikowane.

* Nie mylić, jak w nie rozumiem, ale mylące, ponieważ martwię się, że istnieje jakiś subtelny powód decyzji, której nie rozumiem, i potrzeba kilku mentalnych przeskoków, aby zrozumieć, że "nie, oni właśnie postanowił zrobić to w ten sposób, należy odczekać co ja patrząc na ten kawałek kodu na raz ...”

+7

Fantastyczna odpowiedź - kod to potwierdza! – Mark

+7

Nie jestem pewien, co dzieje się za liniami, ale dla mnie jest dużo czytelniejszy: if (nie any) than if (all not equal). – VikciaR

+1

@VikciaR Tak, zgadzam się. Ogólnie rzecz biorąc, stwierdzam, że (coś) bardziej czytelne niż (jeśli nie coś). Stąd moje przypuszczenie, że właśnie tego szuka reguła, bez niczego, by szukać bardziej konkretnego przypadku, w którym "coś" jest "wszystkie nie równe", a "coś innego" jest "dowolne". Oczywiście, mogę tylko zgadywać, co myśleli o tym re-sharperowcy. –

16

All zwarcia na pierwszym niezgodnym, więc nie jest to problem.

Jednym z obszarów subtelności jest

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

jest prawdą. Wszystkie pozycje w sekwencji są równe.

Aby uzyskać więcej informacji na temat tej metody, zapoznaj się z dokumentacją dla Enumerable.All.

+11

Tak, ale 'bool allEven =! Enumerable.Empty (). Any (i => i% 2! = 0)' jest także prawdziwe. –

+1

@Jon semantycznie brak! = Wszystko. Tak więc semantycznie nie masz żadnego lub wszystkie, ale w przypadku .All() none jest tylko podzbiorem wszystkich kolekcji, które zwracają true dla wszystkich, a ta rozbieżność może spowodować błędy, jeśli nie jesteś tego świadomy. +1 dla tego Anthony –

+0

@RuneFS Nie śledzę. Semantycznie i logicznie "brak tam, gdzie jest nieprawdą, że ..." jest w istocie taki sam jak "wszędzie tam, gdzie jest to prawdą". Na przykład. "gdzie żaden z zaakceptowanych projektów z naszej firmy?" zawsze będzie miał tę samą odpowiedź, co "gdzie wszystkie zaakceptowane projekty z innych firm?" ... –

21

Oba miałyby identyczną wydajność, ponieważ zarówno wyliczenie przystanek po wynikach można stwierdzić - Any() na pierwszej pozycji przekazany orzecznik ocenia się true i All() na pierwszej pozycji orzecznik ocenia się false.

46

można znaleźć te metody przedłużania uczynić kod 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 twojego oryginalnego

można powiedzieć

if (acceptedValues.None(v => v == someValue)) 
{ 
    // exception logic 
} 
+4

Dzięki - już zastanawiałem się nad wprowadzeniem ich w naszej wspólnej bibliotece, ale nie zdecydowałem jeszcze, czy to dobry pomysł. Zgadzam się, że sprawiają one, że kod jest bardziej czytelny, ale obawiam się, że nie stanowią one wystarczającej wartości. – Mark

+0

Szukałem Nic i nie znalazłem. Jest o wiele bardziej czytelny. – Rhyous

+0

Musiałem dodać kontrole zerowe: źródło zwrotu == null || ! source.Any (predykat); – Rhyous

1

Według tego link

Dowolne - Sprawdza przynajmniej jeden mecz

Wszystkie - sprawdza, czy wszystkie mecze

+0

masz rację, ale zatrzymują się w tym samym czasie dla danej kolekcji. Wszystkie przerwy, gdy warunek się nie powiedzie, a wszelkie przerwy, gdy są zgodne z twoim predykatem. Technicznie nie różni się to od scenariusza – WPFKK

2

All() określa, czy wszystkie elementy sekwencji spełniają warunek.
Any() określa, czy dowolny 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 
3

Tak jak inne odpowiedzi są dobrze omówione: nie chodzi o wydajność, chodzi o jasność.

Istnieje szerokie poparcie dla obu opcji:

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

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

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

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

Wystarczy obliczania Boolean (i nazywanie go) przed negując wszystko oczyszcza to wiele w moim umyśle.

3

Jeśli spojrzeć na Enumerable source zobaczysz, że realizacja 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 mowy, że jedna metoda jest znacznie szybsza niż inne, ponieważ tylko różnica polega na negacji boolowskiej, więc preferuj czytelność ponad fałszywą wygraną wydajności.