끝에 mhand에 대한 매우 유용한 주석 후 추가
원래 답변
대부분의 솔루션이 작동 할 수 있지만 효율적이지 않다고 생각합니다. 처음 몇 개의 청크 중 처음 몇 개만 원한다고 가정하십시오. 그런 다음 시퀀스의 모든 (거대한) 항목을 반복하고 싶지 않습니다.
다음은 최대한 두 번 열거됩니다. 테이크와 스킵에 대해 한 번. 사용할 것보다 많은 요소를 열거하지 않습니다.
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 요소를 사용하기로 결정하면 수율 반환은 정확히 M 번 실행됩니다. 이것은 다음을 의미합니다.
- 열거자를 얻는다
- MoveNext () 및 현재 M 번을 호출하십시오.
- 열거자를 폐기하십시오
첫 번째 청크가 반환 된 후 첫 번째 청크를 건너 뜁니다.
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;
}
}
}
보시다시피 청크의 모든 요소에 대해 한 번씩 SkipIterator
호출 MoveNext()
됩니다. 호출하지 않습니다 Current
.
따라서 청크마다 다음이 완료되었음을 알 수 있습니다.
- Any () : GetEnumerator; 1 MoveNext (); 열거자를 처리하십시오.
취하다():
- 청크의 내용이 열거되지 않으면 아무것도 아닙니다.
내용이 열거 된 경우 : GetEnumerator (), 열거 된 항목 당 하나의 MoveNext 및 하나의 현재 항목, Dispose 열거 자;
Skip () : 열거 된 모든 청크에 대해 (청크 내용이 아님) : GetEnumerator (), MoveNext () chunkSize times, no Current! 열거 자 처리
열거 자에 어떤 일이 발생하는지 살펴보면 MoveNext ()에 대한 많은 호출이 있고 Current
실제로 액세스하기로 결정한 TSource 항목 에 대한 호출 만 있음 을 알 수 있습니다.
chunkSize 크기의 N 청크를 사용하는 경우 MoveNext ()를 호출합니다.
- Any ()에 대해 N 회
- 청크를 열거하지 않는 한 테이크 시간은 아직 없습니다.
- Skip ()에 대해 N 배 chunkSize
페치 된 모든 청크의 첫 번째 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는 List 인 객체를 반환하고 DbContext는 데이터를 이미 가져온 것을 기억합니다.
청크에서 항목을 나누기 전에 AsEnumerable () 또는 ToLists ()를 호출하는 것이 현명한 지 여부는 리포지토리에 따라 다릅니다.