LINQ. Qualunque VS. Esiste - Qual è la differenza?


413

Utilizzando LINQ sulle raccolte, qual è la differenza tra le seguenti righe di codice?

if(!coll.Any(i => i.Value))

e

if(!coll.Exists(i => i.Value))

Aggiornamento 1

Quando smonto .Exists, sembra che non ci sia codice.

Aggiornamento 2

Qualcuno sa perché non esiste un codice per questo?


9
Come appare il codice che hai compilato? Come hai smontato? ildasm? Cosa ti aspettavi di trovare ma non lo hai fatto?
Meinersbur,

Risposte:


423

Vedi documentazione

List.Exists (metodo Object - MSDN)

Determina se l'elenco (T) contiene elementi che corrispondono alle condizioni definite dal predicato specificato.

Questo esiste da .NET 2.0, quindi prima di LINQ. Destinato a essere utilizzato con il delegato del predicato , ma le espressioni lambda sono retrocompatibili. Inoltre, solo List ha questo (nemmeno IList)

IEnumerable.Any (Metodo di estensione - MSDN)

Determina se qualsiasi elemento di una sequenza soddisfa una condizione.

Questo è nuovo in .NET 3.5 e utilizza Func (TSource, bool) come argomento, quindi doveva essere usato con espressioni lambda e LINQ.

Nel comportamento, questi sono identici.


4
In seguito ho scritto un post in un altro thread in cui ho elencato tutti gli "equivalenti" Linq dei List<>metodi di istanza .NET 2 .
Jeppe Stig Nielsen il

201

La differenza è che Any è un metodo di estensione per qualsiasi IEnumerable<T>definito su System.Linq.Enumerable. Può essere utilizzato in qualsiasi IEnumerable<T>istanza.

Esiste non sembra essere un metodo di estensione. La mia ipotesi è che coll è di tipo List<T>. In tal caso, esiste un metodo di istanza che funziona in modo molto simile a Qualsiasi.

In breve , i metodi sono essenzialmente gli stessi. Uno è più generale dell'altro.

  • Any ha anche un sovraccarico che non accetta parametri e cerca semplicemente qualsiasi elemento nell'enumerabile.
  • Esiste non ha tale sovraccarico.

13
Ben messo (+1). Elenco <T> .Exists esiste da .Net 2 ma funziona solo per elenchi generici. IEnumerable <T>. Qualsiasi elemento è stato aggiunto in .Net 3 come estensione che funziona su qualsiasi raccolta enumerabile. Ci sono anche membri simili come List <T> .Count, che è una proprietà e IEnumerable <T> .Count () - un metodo.
Keith,

51

TLDR; Per quanto riguarda le prestazioni, Anysembra essere più lento (se l'ho impostato correttamente per valutare entrambi i valori quasi contemporaneamente)

        var list1 = Generate(1000000);
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s +=" Any: " +end1.Subtract(start1);
            }

            if (!s.Contains("sdfsd"))
            {

            }

generatore elenco test:

private List<string> Generate(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            list.Add( new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    new RNGCryptoServiceProvider().GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray())); 
        }

        return list;
    }

Con 10 milioni di record

"Qualsiasi: 00: 00: 00.3770377 Esiste: 00: 00: 00.2490249"

Con 5 milioni di record

"Qualsiasi: 00: 00: 00.0940094 Esiste: 00: 00: 00.1420142"

Con record 1M

"Qualsiasi: 00: 00: 00.0180018 Esiste: 00: 00: 00.0090009"

Con 500k, (ho anche capovolto l'ordine in cui vengono valutati per vedere se non ci sono operazioni aggiuntive associate a qualunque esecuzione per prima.)

"Esiste: 00: 00: 00.0050005 Qualsiasi: 00: 00: 00.0100010"

Con 100.000 record

"Esiste: 00: 00: 00.0010001 Qualsiasi: 00: 00: 00.0020002"

Sembrerebbe Anyessere più lento di magnitudo 2.

Modifica: per i record 5 e 10M ho cambiato il modo in cui genera l'elenco e Existsimprovvisamente sono diventato più lento di quello Anyche implica che c'è qualcosa di sbagliato nel modo in cui sto testando.

Nuovo meccanismo di test:

private static IEnumerable<string> Generate(int count)
    {
        var cripto = new RNGCryptoServiceProvider();
        Func<string> getString = () => new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    cripto.GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());

        var list = new ConcurrentBag<string>();
        var x = Parallel.For(0, count, o => list.Add(getString()));
        return list;
    }

    private static void Test()
    {
        var list = Generate(10000000);
        var list1 = list.ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {

            }
        }

Edit2: Ok, quindi per eliminare qualsiasi influenza dalla generazione dei dati di test ho scritto tutto su un file e ora leggo da lì.

 private static void Test()
    {
        var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {
            }
        }
    }

10M

"Qualsiasi: 00: 00: 00.1640164 Esiste: 00: 00: 00.0750075"

5M

"Qualsiasi: 00: 00: 00.0810081 Esiste: 00: 00: 00.0360036"

1M

"Qualsiasi: 00: 00: 00.0190019 Esiste: 00: 00: 00.0070007"

500k

"Qualsiasi: 00: 00: 00.0120012 Esiste: 00: 00: 00.0040004"

inserisci qui la descrizione dell'immagine


3
Nessun discredito per te, ma mi sento scettico su questi parametri. Guarda i numeri: ogni risultato ha una ricorsione in corso (3770377: 2490249). Almeno per me, questo è un segno sicuro che qualcosa non è corretto. Non sono sicuro al cento per cento in matematica qui, ma penso che le probabilità che questo schema ricorrente si verifichi sia 1 su 999 ^ 999 (o 999! Forse?) Per valore. Quindi la possibilità che ciò accada 8 volte di seguito è infinitesimale. Penso che sia perché usi DateTime per il benchmarking .
Jerri Kangasniemi,

@JerriKangasniemi La ripetizione della stessa operazione in isolamento dovrebbe sempre richiedere la stessa quantità di tempo, lo stesso vale per la ripetizione più volte. Cosa ti fa dire che è DateTime?
Matas Vaitkevicius,

Certo che lo fa. Il problema è ancora che è estremamente improbabile impiegare, ad esempio, 0120012 secondi per chiamate da 500k. E se fosse perfettamente lineare, spiegando così i numeri così bene, le chiamate 1M avrebbero richiesto 0240024 secondi (il doppio della durata), tuttavia non è così. Le chiamate 1M richiedono il 58, (3)% in più di 500k e 10M richiedono il 102,5% in più di 5M. Quindi non è una funzione lineare e quindi non è davvero ragionevole per i numeri da ricorrere a tutti. Ho citato DateTime perché in passato ho riscontrato problemi con esso, a causa del fatto che DateTime non utilizzava timer ad alta precisione.
Jerri Kangasniemi,

2
@JerriKangasniemi Potrei suggerirti di aggiustarlo e pubblicare una risposta
Matas Vaitkevicius,

1
Se sto leggendo correttamente i tuoi risultati, hai segnalato che Any è solo circa 2-3 volte la velocità di Exists. Non vedo come i dati supportino lievemente la tua affermazione che "Sembrerebbe che Any fosse più lento di magnitudo 2". È un po 'più lento, certo, non per ordini di grandezza.
Suncat2000

16

Come continuazione della risposta di Matas sull'analisi comparativa.

TL / DR : Exists () e Any () sono ugualmente veloci.

Prima di tutto: il benchmarking usando il cronometro non è preciso ( vedi la risposta di series0ne su un argomento diverso, ma simile ), ma è molto più preciso di DateTime.

Il modo per ottenere letture davvero precise è utilizzando il Performance Profiling. Ma un modo per avere un'idea di come le prestazioni dei due metodi si misurano l'una con l'altra è eseguendo entrambi i metodi un sacco di volte e quindi confrontando il tempo di esecuzione più veloce di ciascuno. In questo modo, non importa davvero che JIT e altri rumori ci diano cattive letture (e lo fa ), perché entrambe le esecuzioni sono " ugualmente fuorvianti " in un certo senso.

static void Main(string[] args)
    {
        Console.WriteLine("Generating list...");
        List<string> list = GenerateTestList(1000000);
        var s = string.Empty;

        Stopwatch sw;
        Stopwatch sw2;
        List<long> existsTimes = new List<long>();
        List<long> anyTimes = new List<long>();

        Console.WriteLine("Executing...");
        for (int j = 0; j < 1000; j++)
        {
            sw = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw.Stop();
                existsTimes.Add(sw.ElapsedTicks);
            }
        }

        for (int j = 0; j < 1000; j++)
        {
            sw2 = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw2.Stop();
                anyTimes.Add(sw2.ElapsedTicks);
            }
        }

        long existsFastest = existsTimes.Min();
        long anyFastest = anyTimes.Min();

        Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
        Console.WriteLine("Benchmark finished. Press any key.");
        Console.ReadKey();
    }

    public static List<string> GenerateTestList(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            Random r = new Random();
            int it = r.Next(0, 100);
            list.Add(new string('s', it));
        }
        return list;
    }

Dopo aver eseguito il codice sopra riportato 4 volte (che a sua volta fanno 1 000 Exists()e Any()in un elenco con 1 000 000 elementi), non è difficile vedere che i metodi sono praticamente ugualmente veloci.

Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks

Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks

Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks

Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks

V'è una leggera differenza, ma è un differenza troppo piccola per non essere spiegato con il rumore di fondo. La mia ipotesi sarebbe che se uno facesse 10.000 o 100.000Exists() e Any()invece, quella leggera differenza scomparirebbe più o meno.


Potrei suggerire di fare 10.000 e 100.000 e 1000000, solo per essere metodici a riguardo, anche perché valore minimo e non medio?
Matas Vaitkevicius,

2
Il valore minimo è perché voglio confrontare l'esecuzione più veloce (= probabilmente la quantità minima di rumore di fondo) di ciascun metodo. Potrei farlo con più iterazioni, anche se sarà più tardi (dubito che il mio capo voglia pagarmi per fare questo invece di lavorare attraverso il nostro arretrato)
Jerri Kangasniemi,

Ho chiesto a Paul Lindberg e mi ha detto che va bene;) per quanto riguarda il minimo posso vedere il tuo ragionamento, tuttavia un approccio più ortodosso è usare la media en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius

9
Se il codice che hai pubblicato è quello che hai effettivamente eseguito, non sorprende che tu ottenga risultati simili, poiché chiami Esiste in entrambe le misurazioni. ;)
Simon Touchtech,

Heh, sì, l'ho visto anche io adesso lo dici. Non nella mia esecuzione però. Questo era solo inteso come un concetto ridotto di ciò che stavo confrontando. : P
Jerri Kangasniemi,

4

Inoltre, funzionerà solo se Value è di tipo bool. Normalmente questo è usato con predicati. Qualsiasi predicato verrebbe generalmente utilizzato per scoprire se esiste un elemento che soddisfa una determinata condizione. Qui stai solo facendo una mappa dal tuo elemento i a una proprietà bool. Cercherà una "i" la cui proprietà Value è vera. Una volta fatto, il metodo tornerà vero.


3

Quando correggerai le misurazioni - come menzionato sopra: Any and Exist, e aggiungendo media - otterremo il seguente output:

Executing search Exists() 1000 times ... 
Average Exists(): 35566,023
Fastest Exists() execution: 32226 

Executing search Any() 1000 times ... 
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks

Benchmark finished. Press any key.
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.