我一直在寻找一种将foreach
循环分成多个部分的方法,并遇到了以下代码:
foreach(var item in items.Skip(currentPage * itemsPerPage).Take(itemsPerPage))
{
//Do stuff
}
items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)
将在每次迭代中处理,还是只处理一次,并由编译器自动与 foreach 循环一起使用临时结果?
我一直在寻找一种将foreach
循环分成多个部分的方法,并遇到了以下代码:
foreach(var item in items.Skip(currentPage * itemsPerPage).Take(itemsPerPage))
{
//Do stuff
}
items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)
将在每次迭代中处理,还是只处理一次,并由编译器自动与 foreach 循环一起使用临时结果?
不,它会被处理一次。
它是一样的:
public IEnumerable<Something> GetData() {
return someData;
}
foreach(var d in GetData()) {
//do something with [d]
}
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
只会处理一次。
更新:
IEnumerator
请注意,这取决于使用的实现IEnumerable
。
在这个(邪恶的)例子中:
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
两次。
问题:
items.Skip(currentPage * itemsPerPage).Take(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
}
正如其他人指出的那样,您提供的代码只会迭代列表中的项目一次。
但是,这只会为您提供一页的项目。如果您正在处理多个页面,则必须为每个页面调用一次该代码(因为您必须在某处递增currentPage
,对吗?)。
我的意思是你必须做这样的事情:
for (int currentPage = 0; currentPage < numPages; ++currentPage)
{
foreach (var item in items.Skip(currentPage*itemsPerPage).Take(itemsPerPage))
{
//Do stuff
}
}
现在,如果您这样做,那么您将多次迭代序列 - 每页一次。第一次迭代只会到第一页的结尾,但下一次将从第二页的开头迭代到结尾(通过Skip()
和Take()
) - 下一次将从开头迭代到结尾第三页。等等。
为避免这种情况,您可以编写一个扩展方法,IEnumerable<T>
将数据分成批次(您也可以将其描述为将数据“分页”成“页面”)。
与其只显示 IEnumerable 的 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();
}