Tambahan setelah komentar yang sangat berguna dari mhand di akhir
Jawaban asli
Meskipun sebagian besar solusi mungkin berhasil, saya pikir mereka tidak terlalu efisien. Misalkan jika Anda hanya menginginkan beberapa item pertama dari beberapa chunks pertama. Maka Anda tidak ingin mengulangi semua (miliaran) item dalam urutan Anda.
Berikut ini akan paling banyak menyebutkan dua kali: sekali untuk Ambil dan sekali untuk Lewati. Itu tidak akan menghitung lebih dari elemen daripada yang akan Anda gunakan:
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
}
}
Berapa kali ini akan menghitung urutan?
Misalkan Anda membagi sumber Anda menjadi beberapa bagian chunkSize
. Anda hanya menghitung potongan N pertama. Dari setiap potongan yang disebutkan, Anda hanya akan menghitung elemen M pertama.
While(source.Any())
{
...
}
Any akan mendapatkan Enumerator, lakukan 1 MoveNext () dan kembalikan nilai yang dikembalikan setelah Membuang Enumerator. Ini akan dilakukan N kali
yield return source.Take(chunkSize);
Menurut sumber referensi ini akan melakukan sesuatu seperti:
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;
}
}
Ini tidak banyak membantu sampai Anda mulai menghitung lebih dari Chunk yang diambil. Jika Anda mengambil beberapa bongkahan, tetapi memutuskan untuk tidak menghitung lebih dari bongkahan pertama, foreach tidak dieksekusi, karena debugger Anda akan menunjukkan kepada Anda.
Jika Anda memutuskan untuk mengambil elemen M pertama dari chunk pertama maka pengembalian hasil dieksekusi tepat M kali. Ini berarti:
- dapatkan enumerator
- panggil MoveNext () dan M kali ini.
- Buang enumerator
Setelah potongan pertama telah dikembalikan, kami lewati Potongan pertama ini:
source = source.Skip(chunkSize);
Sekali lagi: kita akan melihat sumber referensi untuk menemukanskipiterator
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;
}
}
}
Seperti yang Anda lihat, SkipIterator
panggilan MoveNext()
satu kali untuk setiap elemen di dalam Chunk. Itu tidak menelepon Current
.
Jadi per Chunk kita melihat bahwa berikut ini dilakukan:
- Any (): GetEnumerator; 1 MoveNext (); Buang Enumerator;
Mengambil():
- tidak ada apa-apa jika isi chunk tidak disebutkan.
Jika konten disebutkan: GetEnumerator (), satu MoveNext dan satu Current per item yang disebutkan, Buang enumerator;
Lewati (): untuk setiap chunk yang disebutkan (BUKAN isi chunk): GetEnumerator (), MoveNext () chunkUkuran kali, tanpa arus! Buang enumerator
Jika Anda melihat apa yang terjadi dengan enumerator, Anda akan melihat bahwa ada banyak panggilan ke MoveNext (), dan hanya panggilan ke Current
untuk item TSource yang Anda putuskan untuk akses.
Jika Anda mengambil N Potongan ukuran chunkSize, maka panggilan ke MoveNext ()
- N kali untuk Apa saja ()
- belum waktunya untuk Take, asalkan Anda tidak menyebutkan Chunks
- N kali chunkSize untuk Lewati ()
Jika Anda memutuskan untuk menghitung hanya elemen M pertama dari setiap chunk yang diambil, maka Anda perlu memanggil MoveNext M kali per Chunk yang disebutkan.
Jumlah seluruhnya
MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)
Jadi, jika Anda memutuskan untuk menghitung semua elemen dari semua bongkahan:
MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once
Apakah MoveNext banyak pekerjaan atau tidak, tergantung pada jenis urutan sumber. Untuk daftar dan array, ini adalah kenaikan indeks sederhana, dengan mungkin pemeriksaan di luar rentang.
Tetapi jika IEnumerable Anda adalah hasil dari query database, pastikan bahwa data benar-benar terwujud di komputer Anda, jika tidak, data akan diambil beberapa kali. DbContext dan Dapper akan mentransfer data dengan benar ke proses lokal sebelum dapat diakses. Jika Anda menyebutkan urutan yang sama beberapa kali, itu tidak diambil beberapa kali. Dapper mengembalikan objek yang Daftar, DbContext mengingat bahwa data sudah diambil.
Tergantung pada Repositori Anda apakah bijaksana untuk memanggil AsEnumerable () atau ToLists () sebelum Anda mulai membagi item dalam Potongan