2013-07-03 59 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(); 
}