2013-07-03 94 views
5

我一直在尋找拆分foreach環路成多個部分的方式和整個下面的代碼來:LINQ的優化

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

items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)在每次迭代進行處理,或將對其進行處理一次,編譯器會自動使用foreach循環的臨時結果?

+1

放入中斷點並查看。 –

+0

這只是一個分割。你是否也從循環中調用它? –

回答

6

在foreach建設等同於:

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

所以,不,myCollection將只處理一次。

更新:

請注意,這取決於該IEnumerable用途IEnumerator的實施。

在這種(邪惡)例如:

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

enumerable每次迭代將評估numbers兩次。

9

不,它會被處理一次。

這是相同的,如:

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


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

不確定這是否正確。我的意思是你的GetData函數類似於一個只有getter屬性,並且每次循環遞增時,都會調用get accessor,或者你的方法調用Skip/Take構造。 –

+0

@PotecaruTudor:在foreach循環中,它將被稱爲ones。爲了證明這一點,只需做一個簡單的測試。 – Tigran

+0

是的,只是調試了一個測試例子,你是對的。謝謝。 –

0

問:

將items.Skip(當前頁* itemsPerPage)。取(itemsPerPage)是 處理每一次迭代,或將它處理一次,並與foreach循環使用的 臨時結果編譯器自動通過 ?

答:

它會處理一次,不是每個迭代。您可以將集合放入一個變量中,以使foreach更具可讀性。如下所示。

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

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

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

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

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

我看到代碼塊之間的戰鬥.. –

+0

上面編輯。 :) –

0

,你目前只在迭代列表中的項目一次,正如其他人指出的代碼。

但是,這隻會給你一個頁面的項目。如果你正在處理多個頁面,你必須爲每個頁面調用一次該代碼(因爲某處你必須增加currentPage,對吧?)。

我的意思是,你必須做這樣的事情:

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

現在,如果你這樣做,那麼你將序列多次迭代 - 一次爲每個頁面。第一次迭代只會到第一頁的末尾,但下一次將從第二頁的開始到結束(通過Skip()Take())迭代 - 下一個將從開始迭代到第第三頁結束。等等。

爲了避免這種情況,您可以編寫一個IEnumerable<T>的擴展方法,將數據分割成批(您也可以將其描述爲「分頁」數據到「頁面」)。

而不是僅僅呈現IEnumerables的IEnumerable,它可以是更有用包裹各批次中的一類,以與該批次的物品沿着供應批索引,像這樣:

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

此擴展方法不會緩衝數據,並且只會遍歷一次。

鑑於此擴展方法,對項目進行批處理變得更具可讀性。請注意,此示例列舉了所有頁面的所有項目,而不像OP僅通過一個頁面迭代項目的示例:

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