Linq è più efficiente di quanto appaia sulla superficie?

13

Se scrivo qualcosa del genere:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

È lo stesso di:

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

O c'è qualche magia sotto le coperte che funziona più in questo modo:

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

O è qualcosa di completamente diverso?

    
posta ConditionRacer 29.10.2013 - 16:13
fonte

4 risposte

27

Le query LINQ sono pigri . Ciò significa che il codice:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

fa molto poco. L'enumerazione originale ( mythings ) viene enumerata solo quando viene consumata l'enumerabile risultante ( things ), ad es. di un foreach loop, .ToList() o .ToArray() .

Se chiami things.ToList() , è approssimativamente equivalente al tuo ultimo codice, con forse un sovraccarico (solitamente insignificante) degli enumeratori.

Allo stesso modo, se usi un ciclo foreach:

foreach (var t in things)
    DoSomething(t);

È simile nelle prestazioni a:

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

Alcuni dei vantaggi prestazionali dell'approccio alla pigrizia per enumerabili (al contrario del calcolo di tutti i risultati e della loro memorizzazione in una lista) sono che utilizza pochissima memoria (poiché un solo risultato è memorizzato alla volta) e che c'è nessun costo iniziale significativo.

Se l'enumerabile è enumerato solo parzialmente, ciò è particolarmente importante. Considera questo codice:

things.First();

Il modo in cui LINQ è implementato, mythings sarà enumerato solo fino al primo elemento che corrisponde alle tue condizioni. Se quell'elemento è in cima alla lista, questo può essere un enorme incremento delle prestazioni (ad es. O (1) invece di O (n)).

    
risposta data 29.10.2013 - 16:35
fonte
7

Il seguente codice:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

È equivalente a nulla, a causa della valutazione lenta, non accadrà nulla.

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

È diverso, perché verrà avviata la valutazione.

Ogni elemento di mythings sarà dato al primo Where . Se passa, verrà assegnato al secondo Where . Se passa, sarà parte dell'output.

Quindi sembra più simile a questo:

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}
    
risposta data 29.10.2013 - 16:35
fonte
7

Esecuzione posticipata a parte (che le altre risposte spiegano già, ti indicherò un altro dettaglio), è più simile al tuo secondo esempio.

Immaginiamo che tu chiami ToList su things .

L'implementazione di Enumerable.Where restituisce un Enumerable.WhereListIterator . Quando chiami Where su quel WhereListIterator (a.k.a. concatenando Where -ti), non chiami più Enumerable.Where , ma Enumerable.WhereListIterator.Where , che in realtà combina i predicati (usando Enumerable.CombinePredicates ).

Quindi è più come if(t.IsSomeValue && t.IsSomeOtherValue) .

    
risposta data 29.10.2013 - 16:43
fonte
1

No, non è lo stesso. Nel tuo esempio things è un IEnumerable , che a questo punto è ancora solo un iteratore, non un vero array o elenco. Inoltre, poiché things non viene utilizzato, il ciclo non viene nemmeno valutato. Il tipo IEnumerable consente di scorrere gli elementi yield -ed delle istruzioni di Linq ed elaborarli ulteriormente con più istruzioni, il che significa che alla fine hai solo un ciclo.

Ma non appena aggiungi un'istruzione come .ToArray() o .ToList() , stai ordinando la creazione di una struttura dati effettiva, ponendo quindi limiti alla tua catena.

Vedi questa domanda SO correlata: link

    
risposta data 29.10.2013 - 16:37
fonte

Leggi altre domande sui tag