2013-07-03 9 views
5

sono stato alla ricerca di un modo di dividere un loop foreach in più parti e mi sono imbattuto il seguente codice:Linq ottimizzazione all'interno di un foreach

foreach(var item in items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)) 
{ 
    //Do stuff 
} 

Would items.Skip(currentPage * itemsPerPage).Take(itemsPerPage) essere trattati in ogni iterazione, o sarebbe essere processato una volta, e un risultato temporaneo viene utilizzato automaticamente con il ciclo foreach dal compilatore?

+1

Inserire un punto di interruzione e vedere. –

+0

Questo è solo uno split. Lo stai chiamando anche tu da un ciclo? –

risposta

6

La costruzione foreach è equivalente a:

IEnumerator enumerator = myCollection.GetEnumerator(); 
try 
{ 
    while (enumerator.MoveNext()) 
    { 
     object current = enumerator.Current; 
     Console.WriteLine(current); 
    } 
} 
finally 
{ 
    IDisposable e = enumerator as IDisposable; 
    if (e != null) 
    { 
     e.Dispose(); 
    } 
} 

Quindi, no, myCollection verrà elaborato solo una volta.

Aggiornamento:

prega di notare che questo dipende dalla realizzazione del IEnumerator che i IEnumerable usi.

In questo (male) Esempio:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Collections; 


namespace TestStack 
{ 
    class EvilEnumerator<T> : IEnumerator<T> { 

     private IEnumerable<T> enumerable; 
     private int index = -1; 

     public EvilEnumerator(IEnumerable<T> e) 
     { 
      enumerable = e; 
     } 


     #region IEnumerator<T> Membres 

     public T Current 
     { 
      get { return enumerable.ElementAt(index); } 
     } 

     #endregion 

     #region IDisposable Membres 

     public void Dispose() 
     { 

     } 

     #endregion 

     #region IEnumerator Membres 

     object IEnumerator.Current 
     { 
      get { return enumerable.ElementAt(index); } 
     } 

     public bool MoveNext() 
     { 
      index++; 
      if (index >= enumerable.Count()) 
       return false; 
      return true; 
     } 

     public void Reset() 
     { 

     } 

     #endregion 
    } 
    class DemoEnumerable<T> : IEnumerable<T> 
    { 

     private IEnumerable<T> enumerable; 

     public DemoEnumerable(IEnumerable<T> e) 
     { 
      enumerable = e; 
     } 


     #region IEnumerable<T> Membres 

     public IEnumerator<T> GetEnumerator() 
     { 
      return new EvilEnumerator<T>(enumerable); 
     } 

     #endregion 

     #region IEnumerable Membres 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return this.GetEnumerator(); 
     } 

     #endregion 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      IEnumerable<int> numbers = Enumerable.Range(0,100); 
      DemoEnumerable<int> enumerable = new DemoEnumerable<int>(numbers); 
      foreach (var item in enumerable) 
      { 
       Console.WriteLine(item); 
      } 
     } 
    } 
} 

Ciascuna iterazione su enumerable valuterà numbers due volte.

9

No, verrebbe elaborato una volta.

E 'lo stesso come:

public IEnumerable<Something> GetData() { 
    return someData; 
} 


foreach(var d in GetData()) { 
    //do something with [d] 
} 
+0

Non sicuro se questo è corretto. Voglio dire che la tua funzione GetData è simile a una proprietà getter only, e ogni volta che il loop incrementa il suo passo, chiamerà quel metodo di accesso, o nel tuo caso il metodo, in pratica chiamando la struttura Skip/Take su ogni passaggio. –

+0

@PotecaruTudor: nel ciclo foreach, verrà chiamato ones. Per dimostrarlo, basta fare un semplice test. – Tigran

+0

Sì, ho appena eseguito il debug di un esempio di prova e avevi ragione. Grazie. –

0

Domanda:

Sarebbe items.Skip (currentPage * itemsPerPage) .Take (itemsPerPage) essere elaborati ogni iterazione, o sarebbe eseguito una volta, e hanno un risultato temporaneo utilizzato con il ciclo foreach automaticamente dal compilatore ?

Risposta:

Sarebbe essere elaborato una volta, non ogni iterazione. Puoi mettere la collezione in una variabile per rendere il foreach più leggibile. Illustrato di seguito.

foreach(var item in items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)) 
{ 
    //Do stuff 
} 

vs.

List<MyClass> query = items.Skip(currentPage * itemsPerPage).Take(itemsPerPage).ToList(); 

foreach(var item in query) 
{ 
    //Do stuff 
} 

vs.

IEnumerable<MyClass> query = items.Skip(currentPage * itemsPerPage).Take(itemsPerPage); 

foreach(var item in query) 
{ 
    //Do stuff 
} 
+1

Vedo una battaglia tra i blocchi di codice .. –

+0

Modificato sopra. :) –

0

Il codice che si presenti saranno iterare solo gli elementi della lista una volta, come altri hanno fatto notare.

Tuttavia, questo fornisce solo gli elementi per una pagina. Se gestisci più pagine, devi chiamare quel codice una volta per ogni pagina (perché da qualche parte devi incrementare il numero currentPage, giusto?).

Quello che voglio dire è che si deve fare qualcosa di simile:

for (int currentPage = 0; currentPage < numPages; ++currentPage) 
{ 
    foreach (var item in items.Skip(currentPage*itemsPerPage).Take(itemsPerPage)) 
    { 
     //Do stuff 
    } 
} 

Ora, se lo fai che, allora si avrà essere iterazione i tempi multipli di sequenza - una volta per ogni pagina. La prima iterazione andrà solo fino alla fine della prima pagina, ma la prossima verrà ripetuta dall'inizio alla fine della seconda pagina (tramite lo Skip() e lo Take()) - e la prossima verrà iterata dall'inizio alla fine fine della terza pagina. E così via.

Per evitare che sia possibile scrivere un metodo di estensione per IEnumerable<T> che partiziona i dati in batch (che è anche possibile descrivere come "impaginando" i dati in "pagine").

Piuttosto che presentando un'IEnumerable di IEnumerables, può essere più utile per avvolgere ogni lotto in una classe di fornire l'indice lotto insieme ai componenti di una partita, in questo modo:

public sealed class Batch<T> 
{ 
    public readonly int Index; 
    public readonly IEnumerable<T> Items; 

    public Batch(int index, IEnumerable<T> items) 
    { 
     Index = index; 
     Items = items; 
    } 
} 

public static class EnumerableExt 
{ 
    // Note: Not threadsafe, so not suitable for use with Parallel.Foreach() or IEnumerable.AsParallel() 

    public static IEnumerable<Batch<T>> Partition<T>(this IEnumerable<T> input, int batchSize) 
    { 
     var enumerator = input.GetEnumerator(); 
     int index = 0; 

     while (enumerator.MoveNext()) 
      yield return new Batch<T>(index++, nextBatch(enumerator, batchSize)); 
    } 

    private static IEnumerable<T> nextBatch<T>(IEnumerator<T> enumerator, int blockSize) 
    { 
     do { yield return enumerator.Current; } 
     while (--blockSize > 0 && enumerator.MoveNext()); 
    } 
} 

Questa estensione il metodo non memorizza i dati nel buffer e lo esegue solo una volta.

Dato questo metodo di estensione, diventa più leggibile raggruppare gli articoli. Si noti che questo esempio enumera attraverso TUTTI gli articoli per tutte le pagine, a differenza dell'esempio dell'OP che scorre solo attraverso gli elementi per una pagina:

var items = Enumerable.Range(10, 50); // Pretend we have 50 items. 
int itemsPerPage = 20; 

foreach (var page in items.Partition(itemsPerPage)) 
{ 
    Console.Write("Page " + page.Index + " items: "); 

    foreach (var i in page.Items) 
     Console.Write(i + " "); 

    Console.WriteLine(); 
} 
Problemi correlati