Sonunda mhand çok yararlı bir yorumdan sonra ekleme
Orijinal cevap
Çoğu çözüm işe yarasa da, bence çok verimli değiller. İlk birkaç parçanın yalnızca ilk birkaç öğesini istiyorsanız varsayalım. O zaman dizinizdeki tüm (milyon) öğeyi yinelemek istemezsiniz.
Aşağıdakiler en fazla iki kez numaralandırılacaktır: biri Take için, diğeri Skip için. Kullanacağınızdan daha fazla öğe üzerinde numaralandırılmayacaktır:
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
}
}
Bu diziyi kaç kez numaralandırır?
Varsayalım ki kaynağınız chunkSize
. Sadece ilk N parçasını numaralandırıyorsunuz. Her numaralandırılmış parçadan yalnızca ilk M öğelerini numaralandıracaksınız.
While(source.Any())
{
...
}
Herhangi biri, Numaralandırıcıyı alır, 1 MoveNext () işlemi yapar ve Numaralandırıcıyı Ortadan Kaldırdıktan sonra döndürülen değeri döndürür. Bu N kez yapılacak
yield return source.Take(chunkSize);
Göre referans kaynağı böyle bir şey yapar:
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;
}
}
Getirilen Chunk üzerinde numaralandırmaya başlayıncaya kadar bu pek bir şey yapmaz. Birkaç parça alırsanız, ancak ilk yığın üzerinde numaralandırmamaya karar verirseniz, hata ayıklayıcının size göstereceği için foreach yürütülmez.
İlk yığının ilk M öğelerini almaya karar verirseniz, verim dönüşü tam olarak M kez yürütülür. Bu şu anlama gelir:
- numaralandırıcıyı al
- MoveNext () ve Current M sürelerini çağırın.
- Numaralandırıcıyı atın
İlk yığın geri getirildikten sonra, bu ilk yığın'ı atlarız:
source = source.Skip(chunkSize);
Yine kez: biz bakacağız referans kaynağı bulmak içinskipiterator
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;
}
}
}
Gördüğünüz gibi , Chunk'taki her öğe için SkipIterator
çağrılar bir MoveNext()
kez. AramıyorCurrent
.
Yani Chunk'a göre aşağıdakilerin yapıldığını görüyoruz:
- Herhangi biri (): GetEnumerator; 1 MoveNext (); Numaralandırıcıyı atın;
) (Atın:
- yığın içeriği numaralandırılmazsa hiçbir şey.
İçerik numaralandırılırsa: GetEnumerator (), numaralandırılmış öğe başına bir MoveNext ve bir Current, numaralandırıcıyı imha et;
Skip (): numaralandırılan her yığın için (yığının içeriği DEĞİL): GetEnumerator (), MoveNext () chunkSize kez, Current yok! Numaralandırıcıyı atın
Sayıcıda neler olduğuna bakarsanız, MoveNext () 'e çok sayıda çağrı olduğunu görürsünüz ve yalnızca Current
gerçekten erişmeye karar verdiğiniz TSource öğeleri için yaparsınız.
ChunkSize boyutunda N Chunks alırsanız, MoveNext () işlevini çağırır
- Herhangi biri için N kez ()
- Topaklar için numaralandırmadığınız sürece Take için henüz zaman yok
- N kere chunkSize for Skip ()
Her getirilen yığının yalnızca ilk M öğelerini numaralandırmaya karar verirseniz, numaralandırılmış Chunk başına MoveNext M kez çağırmanız gerekir.
Toplam
MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)
Dolayısıyla, tüm parçaların tüm öğelerini numaralandırmaya karar verirseniz:
MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once
MoveNext'in çok çalışıp çalışmadığı kaynak dizisinin türüne bağlıdır. Listeler ve diziler için, aralık dışı bir kontrolle basit bir dizin artışıdır.
Ancak IEnumerable'iniz bir veritabanı sorgusunun sonucuysa, verilerin gerçekten bilgisayarınızda gerçekleştiğinden emin olun, aksi takdirde veriler birkaç kez alınır. DbContext ve Dapper, erişilmeden önce verileri yerel sürece doğru şekilde aktarır. Aynı diziyi birkaç kez numaralandırırsanız, birkaç kez getirilmez. Dapper Liste olan bir nesneyi döndürür, DbContext verinin önceden getirildiğini hatırlar.
Parçaları Chunks'ta bölmeye başlamadan önce AsEnumerable () veya ToLists () öğesini çağırmanın akıllıca olup olmadığı Havuzunuza bağlıdır.