2015-12-03 14 views
21

Nella seguente test:LINQ Skip enumera ancora elementi ignorati

int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 
Func<int, int> boom = x => { Console.WriteLine(x); return x; }; 
var res = data.Select(boom).Skip(3).Take(4).ToList(); 
Console.WriteLine(); 
res.Select(boom).ToList(); 

Il risultato è:

1 
2 
3 
4 
5 
6 
7 

4 
5 
6 
7 

Essenzialmente, ho osservato che in questo esempio, Skip() e Take() funzionano bene, Skip() non è come pigro come Take(). Sembra che Skip() annulli ancora gli elementi ignorati, anche se non li restituisce.

Lo stesso vale se faccio prima Take(). La mia ipotesi migliore è che sia necessario enumerare almeno il primo salto o prendere, per vedere dove andare con il prossimo.

Perché lo fa?

+4

Bene, 'Skip (n)' funziona richiedendo l'elemento successivo, 'n volte. Il prossimo elemento viene dalla tua selezione. Alcune raccolte potrebbero avere senso "saltare avanti", ma non tutte. Cosa succede se la raccolta è stata generata da un enumeratore personalizzato? – Rob

+4

"Salta" non aumenta l'indice di 'n' o qualcosa del genere. 'Skip' sta semplicemente dicendo all'enumeratore" dare un elemento successivo "' n volte senza memorizzare questi valori in una matrice di risultati. –

+1

@YeldarKurmangaliyev, penso che la tua spiegazione abbia più senso. @Rob, infatti, un enumeratore personalizzato che in realtà non ispeziona mai l'elemento pigro sarebbe meglio qui. Penso che sia così che Skip/Take dovrebbe funzionare. Ma l'iteratore in 'Where()' è ancora peggio. – ericosg

risposta

22

Skip() e Take() entrambi funzionano su IEnumerable<>.

IEnumerable<> non supporta il salto in avanti - può darti solo un articolo alla volta. Con questo in mente, puoi pensare allo Skip() di più come un filtro: tocca ancora tutti gli elementi nella sequenza sorgente, ma filtra comunque a quanti te lo dici. E, soprattutto, li esclude dall'ottenere quello che è il prossimo, non per quello che gli sta di fronte.

Quindi, in questo modo:

data.Select(boom).Skip(3) 

si sta eseguendo boom() su ogni elemento prima ottengono al filtro Skip().

Se invece stata modificata a questo, sarebbe filtrare prima del Select e si sarebbe chiamata boom() solo sugli elementi rimanenti:

data.Skip(3).Take(4).Select(boom) 
+0

Suppongo che sia ancora meglio di "Dove", dal momento che itera su tutti e 10 per ottenere l'iteratore. cioè 'var res = data.Select (boom) .Where ((x, i) => i> = 3 && i <7) .ToList();' vs 'var res = data.Where ((x, i) => i> = 3 && i <7). Select (boom) .ToList(); ' – ericosg

+1

Non dovrebbe esserci virtualmente alcuna differenza tra' Where() 'e' Skip() 'in termini di elementi toccati. Ci sarà una marcata differenza tra 'Where()' e 'Take()', però .. –

+0

in effetti è così. – ericosg

1

assumendo ho capito bene la tua domanda.

la funzione asta viene eseguita prima dell'operazione di salto.

prova a selezionare i dati dopo saltare e prendere, si otterrà l'esatto.

+0

Sì, lo so, se Salto/Prendi dopo la mia selezione funziona come previsto, ma il mio obiettivo è il perché il visto non lo è. – ericosg

+0

in arrivo per la tua domanda, mentre esegui il risultato per var res = data.Select (boom) .Skip (3) .Take (4) .ToList(); la funzione boom viene eseguita sette volte totalmente (3 per saltare e 4 per riprendere) in modo da stampare. e il risultato finale dell'array filtrato. –

2

Entrambi iterano la raccolta. E poi l'affluenza con la raccolta finale. È proprio come un semplice for loop con una condizione if else al suo interno.

Inoltre lo stai selezionando prima usando boom, stampa l'elemento di raccolta.

Con questo esempio non è possibile sapere se skip() o take() itera l'intera raccolta o meno, ma la realtà è che lo fanno.

5

Se decompilare Enumerable, si vedrà la seguente implementazione di Skip:

while (count > 0 && e.MoveNext()) 
    --count; 

e la successiva realizzazione di Take:

foreach (TSource source1 in source) 
{ 
    yield return source1; 
    if (--count == 0) break; 
} 

Così, entrambi i metodi di LINQ in realtà enumerare questi elementi. La differenza è se un elemento enumerato verrà inserito nella raccolta risultante oppure no. È così che funziona IEnumerable.

+1

Il tuo decompilatore ha trovato un modo veramente prolisso per esprimere l'interruzione di 'if (--count == 0);' –

+1

L'implementazione del metodo Skip è [qui] (http://referencesource.microsoft.com/#System.Core/System/ Linq/Enumerable.cs, 90ccdbd47b57182e, riferimenti) - referenceource. –

+0

@CoryNelson Grazie per il miglioramento :) Ho modificato la mia risposta. Ecco come funziona Resharper. –

0

Non una risposta, ma pensare a come sarebbe implementato Skip(...). Ecco un modo per farlo:

public static IEnumerable<T> Skip<T>(this IEnumerable<T> list, int count) 
{ 
    foreach (var item in list) 
    { 
     if (count > 0) count--; 
     else yield return item; 
    } 
} 

Si noti che l'intera list argomento viene enumerato anche se solo un sottoinsieme viene restituito.

1

Questo comportamento potrebbe essere modificato in futuro, come si può vedere in questa discussione su una richiesta pull che ottimizza il comportamento del metodo Skip per le sorgenti che implementano l'interfaccia IList.

Problemi correlati