LINQ: No Any vs All Don't


272

Spesso voglio verificare se un valore fornito corrisponde a uno in un elenco (ad es. Durante la convalida):

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

Di recente, ho notato ReSharper che mi chiede di semplificare queste query per:

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

Ovviamente, questo è logicamente identico, forse leggermente più leggibile (se hai fatto molta matematica), la mia domanda è: questo si traduce in un hit da prestazione?

Sembra che dovrebbe (cioè .Any()sembra cortocircuito, mentre .All()sembra che non lo sia), ma non ho nulla per dimostrarlo. Qualcuno ha una conoscenza più approfondita se le query risolveranno lo stesso o se ReSharper mi sta portando fuori strada?


6
Hai provato a smontare il codice Linq per vedere cosa sta facendo?
RQDQ,

9
In questo caso andrei davvero con if (! AcceptValues.Contains (someValue)), ma ovviamente questa non era la domanda :)
csgero,

2
@csgero Sono d'accordo. Quanto sopra era una semplificazione (forse un'eccessiva semplificazione) della logica reale.
Segna il

1
"Sembra che dovrebbe (cioè .Any () suona come un cortocircuito, mentre .All () suona come non lo fa" "- Non per nessuno con intuizioni sonore. L'equivalenza logica che noti implica che sono ugualmente cortocircuitabili. Un momento di riflessione rivela che tutti possono smettere non appena si incontra un caso non qualificato.
Jim Balter,

3
Non sono universalmente d'accordo con ReSharper su questo. Scrivi sensati treni di pensiero. Se si desidera generare un'eccezione se un elemento richiesto è mancante: if (!sequence.Any(v => v == true)). Se si desidera continuare solo se tutto è conforme a una certa specifica: if (sequence.All(v => v < 10)).
Timo,

Risposte:


344

Implementazione di Allsecondo ILSpy (come in realtà sono andato e guardato, piuttosto che "bene, quel metodo funziona un po 'come ..." Potrei fare se discutessimo della teoria piuttosto che dell'impatto).

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

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

Naturalmente, potrebbe esserci qualche sottile differenza nell'IL prodotta. Ma no, no non c'è. L'IL è praticamente lo stesso, ma per l'ovvia inversione di restituire true su match predicato rispetto a restituire false su predication mismatch.

Ovviamente questo è linq-per-oggetti. È possibile che alcuni altri provider linq trattino uno molto meglio dell'altro, ma se così fosse, è praticamente casuale quale ottenga l'implementazione più ottimale.

Sembrerebbe che la regola scenda solo a qualcuno che if(determineSomethingTrue)si sente più semplice e più leggibile di if(!determineSomethingFalse). E in tutta onestà, penso che abbiano un po 'di senso nel fatto che trovo spesso if(!someTest)confuso * quando c'è un test alternativo di uguaglianza e complessità uguali che tornerebbe vero per la condizione su cui vogliamo agire. Eppure, personalmente non trovo nulla che favorisca l'una rispetto all'altra delle due alternative che dai, e forse mi spingerei leggermente verso la prima se il predicato fosse più complicato.

* Non confuso come in non capisco, ma confuso come in temo che ci sia qualche sottile motivo per la decisione che non capisco, e ci vogliono alcuni salti mentali per rendersi conto che "no, hanno appena deciso di fare in quel modo, aspetta di nuovo cosa stavo guardando questo codice? ... "


8
Non sono sicuro di ciò che viene fatto dietro le righe, ma per me è molto più leggibile è: if (non qualsiasi) rispetto a if (tutti non uguali).
VikciaR,

49
C'è una GRANDE differenza quando la tua enumerazione non ha valori. "Any" restituisce sempre FALSE e "All" restituisce sempre TRUE. Quindi dire che uno è l'equivalente logico dell'altro non è del tutto vero!
Arnaud,

44
@Arnaud Anytornerà falsee quindi !Anytornerà true, quindi sono identici.
Jon Hanna,

11
@Arnaud Non c'è nessuno che abbia commentato che abbia detto che Any e All sono logicamente equivalenti. O per dirla in altro modo, tutte le persone che hanno commentato non hanno detto che Any e All sono logicamente equivalenti. L'equivalenza è tra! Any (predicato) e All (! Predicato).
Jim Balter,

7
@MacsDickinson non è affatto una differenza, perché non stai confrontando predicati opposti. L'equivalente a !test.Any(x => x.Key == 3 && x.Value == 1)tali usi Allè test.All(x => !(x.Key == 3 && x.Value == 1))(che è effettivamente equivalente a test.All(x => x.Key != 3 || x.Value != 1)).
Jon Hanna,

55

Potresti trovare questi metodi di estensione che rendono il tuo codice più leggibile:

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

Ora invece del tuo originale

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

potresti dire

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

6
Grazie - Stavo già pensando di implementarli nella nostra biblioteca comune, ma non ho ancora deciso se è una buona idea. Sono d'accordo che rendono il codice più leggibile, ma temo che non aggiungano un valore sufficiente.
Mark

2
Non ho cercato nessuno e non l'ho trovato. È molto più leggibile.
Rhyous,

Ho dovuto aggiungere controlli null: return source == null || ! source.Any (predicato);
Rhyous,

27

Entrambi avrebbero prestazioni identiche perché entrambi arrestano l'enumerazione dopo che è possibile determinare il risultato: Any()sul primo elemento il predicato passato valuta truee All()sul primo elemento al quale il predicato valuta false.


21

All cortocircuiti sul primo non-match, quindi non è un problema.

Un'area di sottigliezza è quella

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

È vero. Tutti gli elementi nella sequenza sono pari.

Per ulteriori informazioni su questo metodo, consultare la documentazione di Enumerable.All .


12
Sì, ma bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)è anche vero.
Jon Hanna,

1
@Jon semanticamente nessuno! = Tutto. Quindi semanticamente non hai nessuno o tutti, ma nel caso di .All () none è solo un sottoinsieme di tutte le raccolte che tornano vere per tutti e che la discrepanza può causare bug se non ne sei consapevole. +1 per quel Anthony
Rune FS

@RuneFS Non lo seguo. Semanticamente e logicamente "nessuno dove non è vero che ..." è effettivamente lo stesso di "tutto dove è vero che". Ad esempio "dove nessuno dei progetti accettati dalla nostra azienda?" avrà sempre la stessa risposta di "dove tutti i progetti accettati da altre aziende?" ...
Jon Hanna,

... Ora, è vero che puoi avere bug supponendo che "tutti gli articoli siano ..." significa che c'è almeno un oggetto che è almeno un oggetto che soddisfa il test, dal momento che "tutti gli oggetti ... "è sempre vero per il set vuoto, non lo contendo affatto. Ho aggiunto però che lo stesso problema può verificarsi supponendo che "nessuno degli articoli ..." significhi che almeno un articolo non soddisfa il test, poiché "nessuno degli articoli ..." è sempre vero anche per l'insieme vuoto . Non è che non sono d'accordo con il punto di Anthony, è che penso che valga anche per l'altro dei due costrutti in discussione.
Jon Hanna,

@Jon stai parlando di logica e io sto parlando di linguistica. Il cervello umano non può elaborare un negativo (prima che elabori il positivo a quel punto può quindi negarlo), in tal senso c'è una bella differenza tra i due. Ciò non rende la logica che proponi errata
Rune FS

8

All()determina se tutti gli elementi di una sequenza soddisfano una condizione.
Any()determina se qualsiasi elemento di una sequenza soddisfa la condizione.

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

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

7

Secondo questo link

Qualsiasi: verifica almeno una corrispondenza

Tutti: controlla che tutti corrispondano


1
hai ragione ma si fermano allo stesso tempo per una determinata collezione. Tutte le interruzioni quando la condizione fallisce e tutte le interruzioni quando corrispondono al predicato. Quindi tecnicamente non è diverso se non scenicamente
WPFKK

6

Come altre risposte hanno ben coperto: non si tratta di prestazioni, ma di chiarezza.

C'è un ampio supporto per entrambe le opzioni:

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

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

Ma penso che questo potrebbe ottenere un supporto più ampio :

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

Semplicemente calcolare il booleano (e nominarlo) prima di negare qualsiasi cosa mi chiarisce molto nella mente.


3

Se dai un'occhiata alla fonte Enumerable vedrai che l'implementazione di Anyed Allè abbastanza vicina:

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

Non è possibile che un metodo sia significativamente più veloce dell'altro poiché l'unica differenza risiede in una negazione booleana, quindi preferisci la leggibilità rispetto alle vincite con prestazioni false.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.