5

ループを複数の部分に分割する方法を探していたところforeach、次のコードに出会いました。

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

items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)すべての反復で処理されますか、それとも一度処理され、一時的な結果が foreach ループでコンパイラによって自動的に使用されますか?

4

4 に答える 4

9

いいえ、一度処理されます。

それは同じです:

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


foreach(var d in GetData()) {
   //do something with [d]
}
于 2013-07-03T08:05:31.727 に答える
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);
            }
        }
    }
}

各反復は2 回enumerable評価されます。numbers

于 2013-07-03T08:08:24.717 に答える
0

質問:

items.Skip(currentPage * itemsPerPage).Take(itemsPerPage) は反復ごとに処理されますか、それとも 1 回処理され、コンパイラによって自動的に 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
}
于 2013-07-03T08:08:31.917 に答える
0

提示するコードは、他の人が指摘したように、リスト内の項目を 1 回だけ反復します。

ただし、これでは 1 ページ分のアイテムしか表示されません。複数のページを処理している場合は、そのコードをページごとに 1 回呼び出す必要があります (どこかで をインクリメントする必要があるためですよねcurrentPage?)。

つまり、次のようなことをしている必要があります。

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

これを行うと、シーケンスを複数回 (各ページに 1 回) 反復することになります最初の反復は最初のページの終わりまでしか進みませんが、次は 2 ページ目の最初から最後まで ( と を介して)反復し、次は最初から最後まで反復します。 3ページ目。等々。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());
    }
}

この拡張メソッドはデータをバッファリングせず、一度だけ反復します。

この拡張メソッドを使用すると、アイテムをまとめて読みやすくなります。この例は、1 ページのアイテムのみを反復する 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();
}
于 2013-07-03T08:31:50.867 に答える