Linq优化内的一个foreach

时间:2013-07-03 08:04:24

标签: c# linq optimization foreach

我一直在寻找一种将foreach循环分成多个部分的方法,并且遇到了以下代码:

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

会在每次迭代中处理items.Skip(currentPage * itemsPerPage).Take(itemsPerPage),还是会处理一次,并且编译器会自动使用foreach循环的临时结果?

4 个答案:

答案 0 :(得分:9)

不,它会被处理一次。

就像:

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


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

答案 1 :(得分: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只会被处理一次。

<强>更新

请注意,这取决于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两次。

答案 2 :(得分:0)

问题:

  

items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)是   处理每次迭代,或者它会被处理一次,并且有一个   临时结果与foreach循环一起自动使用   编译器?

答案:

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

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
}

答案 3 :(得分:0)

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

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

我的意思是你必须做这样的事情:

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

现在,如果您,那么 将多次迭代序列 - 每个页面一次。第一次迭代只会到第一页的末尾,但是下一次迭代将从第二页的开头到结尾迭代(通过Skip()Take()) - 并且next将从第三页的开头到结尾迭代。等等。

为了避免这种情况,您可以为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();
}