C# foreach中的Linq优化

C# foreach中的Linq优化,c#,linq,optimization,foreach,C#,Linq,Optimization,Foreach,我一直在寻找将foreach循环拆分为多个部分的方法,并遇到了以下代码: foreach(var item in items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)) { //Do stuff } items.Skip(currentPage*itemsPerPage).Take(itemsPerPage)会在每次迭代中处理吗?还是只处理一次,并由编译器自动将临时结果用于foreach循环?否,只处理一次 这是一样的: p

我一直在寻找将
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]
}
public IEnumerable GetData(){
返回一些数据;
}
foreach(GetData()中的变量d){
//用[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
将只处理一次

更新:

请注意,这取决于
IEnumerable
使用的
IEnumerable
的实现

在这个(邪恶的)例子中:

使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统集合;
命名空间测试堆栈
{
类枚举器:IEnumerator{
私有IEnumerable可枚举;
私有整数指数=-1;
公共枚举器(IEnumerable e)
{
可枚举=e;
}
#区域IEnumerator Membres
公共电流
{
获取{return enumerable.ElementAt(index);}
}
#端区
#区域可识别记忆
公共空间处置()
{
}
#端区
#区域IEnumerator Membres
对象IEnumerator.Current
{
获取{return enumerable.ElementAt(index);}
}
公共图书馆
{
索引++;
如果(索引>=enumerable.Count())
返回false;
返回true;
}
公共无效重置()
{
}
#端区
}
类DemoEnumerable:IEnumerable
{
私有IEnumerable可枚举;
公共可取消枚举(IEnumerable e)
{
可枚举=e;
}
#区域数模
公共IEnumerator GetEnumerator()
{
返回新的枚举数(可枚举);
}
#端区
#区域数模
IEnumerator IEnumerable.GetEnumerator()
{
返回此.GetEnumerator();
}
#端区
}
班级计划
{
静态void Main(字符串[]参数)
{
IEnumerable numbers=可枚举范围(0100);
DemoEnumerable enumerable=新的DemoEnumerable(数字);
foreach(可枚举中的变量项)
{
控制台写入线(项目);
}
}
}
}
在可枚举的
上的每次迭代将对
数字进行两次评估。

问题:

items.Skip(当前页面*itemsPerPage).Take(itemsPerPage)是否为 处理每一次迭代,还是只处理一次,然后 由自动与foreach循环一起使用的临时结果 编译程序

答复:

它将被处理一次,而不是每次迭代。您可以将集合放入变量中,以使foreach更具可读性。如下图所示

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

List query=items.Skip(当前页面*itemsPerPage).Take(itemsPerPage.ToList();
foreach(查询中的var项)
{
//做事
}
vs

IEnumerable query=items.Skip(当前页面*itemsPerPage).Take(itemsPerPage);
foreach(查询中的var项)
{
//做事
}

正如其他人所指出的,您提供的代码只会迭代列表中的项目一次

但是,这仅为您提供一页的项目。如果要处理多个页面,则必须为每个页面调用该代码一次(因为您必须在某个地方递增
currentPage
,对吗?)

我的意思是你一定在做这样的事情:

for (int currentPage = 0; currentPage < numPages; ++currentPage)
{
    foreach (var item in items.Skip(currentPage*itemsPerPage).Take(itemsPerPage))
    {
        //Do stuff
    }
}
此扩展方法不缓冲数据,只对数据进行一次迭代

有了这种扩展方法,对项目进行批处理变得更具可读性。请注意,此示例枚举所有页面的所有项,而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();
}

放一个断点进去看看,这只是一次分裂。你也是从循环中调用的吗?不确定这是否正确。我的意思是,GetData函数类似于仅getter属性,每次循环增加其步骤时,都将调用该get访问器,或者在您的情况下,您的方法基本上是在每个步骤上调用Skip/Take构造。@PotecaruTudor:在foreach循环中,它将被称为One。为了证明这一点,只需做一个简单的测试。是的,刚刚调试了一个测试示例,你是对的。谢谢
IEnumerable<MyClass> query = items.Skip(currentPage * itemsPerPage).Take(itemsPerPage);

foreach(var item in query)
{
    //Do stuff
}
for (int currentPage = 0; currentPage < numPages; ++currentPage)
{
    foreach (var item in items.Skip(currentPage*itemsPerPage).Take(itemsPerPage))
    {
        //Do stuff
    }
}
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());
    }
}
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();
}