最後にmhandの非常に有用なコメントの後の追加
元の回答
ほとんどのソリューションは機能するかもしれませんが、あまり効率的ではないと思います。最初のいくつかのチャンクの最初のいくつかのアイテムのみが必要な場合を想定します。次に、シーケンス内のすべての(無数の)アイテムを反復処理する必要はありません。
以下は、最大で2回列挙されます。1回はテイク用、もう1回はスキップ用です。使用する要素よりも多くの要素を列挙することはありません。
public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>
(this IEnumerable<TSource> source, int chunkSize)
{
while (source.Any()) // while there are elements left
{ // still something to chunk:
yield return source.Take(chunkSize); // return a chunk of chunkSize
source = source.Skip(chunkSize); // skip the returned chunk
}
}
これは何回シーケンスを列挙しますか?
ソースをのチャンクに分割するとしますchunkSize
。最初のN個のチャンクのみを列挙します。列挙されたすべてのチャンクから、最初のM要素のみを列挙します。
While(source.Any())
{
...
}
Anyは列挙子を取得し、1 MoveNext()を実行して、列挙子を破棄した後に戻り値を返します。これはN回行われます
yield return source.Take(chunkSize);
参照ソースによると、これは次のようになります。
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
return TakeIterator<TSource>(source, count);
}
static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
foreach (TSource element in source)
{
yield return element;
if (--count == 0) break;
}
}
フェッチされたチャンクを列挙し始めるまで、これはあまり効果がありません。複数のチャンクをフェッチしたが、最初のチャンクを列挙しないことにした場合、デバッガーに表示されるように、foreachは実行されません。
最初のチャンクの最初のM個の要素を取得することにした場合、yieldreturnは正確にM回実行されます。これの意味は:
- 列挙子を取得します
- MoveNext()と現在のM回を呼び出します。
- 列挙子を破棄します
最初のチャンクがyieldに返された後、この最初のチャンクをスキップします。
source = source.Skip(chunkSize);
もう一度:参照ソースを見て、skipiterator
static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
while (count > 0 && e.MoveNext()) count--;
if (count <= 0)
{
while (e.MoveNext()) yield return e.Current;
}
}
}
ご覧のとおり、チャンク内のすべての要素に対して1回SkipIterator
呼び出します。は呼び出されません。MoveNext()
Current
したがって、チャンクごとに、次のことが行われていることがわかります。
- Any():GetEnumerator; 1 MoveNext(); 列挙子を破棄します。
取った():
列挙子で何が起こるかを見ると、MoveNext()への呼び出しが多く、Current
実際にアクセスすることを決定したTSourceアイテムの呼び出しのみが行われていることがわかります。
サイズchunkSizeのチャンクをN個取得する場合は、MoveNext()を呼び出します。
- Any()の場合はN回
- チャンクを列挙しない限り、Takeの時間はまだありません
- Skip()のchunkSizeのN倍
フェッチされたすべてのチャンクの最初のM要素のみを列挙する場合は、列挙されたチャンクごとにMoveNextをM回呼び出す必要があります。
合計
MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)
したがって、すべてのチャンクのすべての要素を列挙することにした場合は、次のようになります。
MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once
MoveNextが多くの作業を行うかどうかは、ソースシーケンスのタイプによって異なります。リストと配列の場合、これは単純なインデックスインクリメントであり、範囲外のチェックが行われる可能性があります。
ただし、IEnumerableがデータベースクエリの結果である場合は、データが実際にコンピュータ上で実体化されていることを確認してください。そうでない場合、データは数回フェッチされます。DbContextとDapperは、データにアクセスする前に、データをローカルプロセスに適切に転送します。同じシーケンスを数回列挙した場合、それは数回フェッチされません。Dapperはリストであるオブジェクトを返します。DbContextはデータがすでにフェッチされていることを記憶しています。
チャンクでアイテムを分割し始める前に、AsEnumerable()またはToLists()を呼び出すのが賢明かどうかは、リポジトリによって異なります。