Ngoài ra sau khi nhận xét rất hữu ích của mhand ở cuối
Câu trả lời gốc
Mặc dù hầu hết các giải pháp có thể hoạt động, tôi nghĩ rằng chúng không hiệu quả lắm. Giả sử nếu bạn chỉ muốn một vài mục đầu tiên của một vài khối đầu tiên. Sau đó, bạn sẽ không muốn lặp lại tất cả (hàng triệu) mục trong chuỗi của mình.
Sau đây sẽ liệt kê tối đa hai lần: một lần cho Lấy và một lần cho Bỏ qua. Nó sẽ không liệt kê bất kỳ yếu tố nào nhiều hơn bạn sẽ sử dụng:
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
}
}
Bao nhiêu lần điều này sẽ liệt kê trình tự?
Giả sử bạn chia nguồn của bạn thành nhiều phần chunkSize
. Bạn liệt kê chỉ N khối đầu tiên. Từ mỗi khối được liệt kê, bạn sẽ chỉ liệt kê các phần tử M đầu tiên.
While(source.Any())
{
...
}
Bất kỳ sẽ nhận được Trình liệt kê, thực hiện 1 MoveNext () và trả về giá trị được trả về sau khi Loại bỏ Trình liệt kê. Điều này sẽ được thực hiện N lần
yield return source.Take(chunkSize);
Theo nguồn tham khảo này sẽ làm một cái gì đó như:
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;
}
}
Điều này không làm được gì nhiều cho đến khi bạn bắt đầu liệt kê Chunk tìm nạp. Nếu bạn tìm nạp một vài Chun, nhưng quyết định không liệt kê Chunk đầu tiên, thì foreach không được thực thi, vì trình gỡ lỗi của bạn sẽ hiển thị cho bạn.
Nếu bạn quyết định lấy các phần tử M đầu tiên của đoạn đầu tiên thì lợi nhuận mang lại được thực hiện chính xác M lần. Điều này có nghĩa là:
- lấy điều tra viên
- gọi MoveNext () và M lần hiện tại.
- Vứt bỏ điều tra viên
Sau khi phần đầu tiên đã được trả lại, chúng ta bỏ qua phần đầu tiên này:
source = source.Skip(chunkSize);
Một lần nữa: chúng ta sẽ xem nguồn tham khảo để tìmskipiterator
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;
}
}
}
Như bạn thấy, các SkipIterator
cuộc gọi MoveNext()
một lần cho mọi phần tử trong Chunk. Nó không gọi Current
.
Vì vậy, mỗi Chunk chúng ta thấy rằng những điều sau đây được thực hiện:
- Bất kỳ (): GetEnumerator; 1 MoveNext (); Vứt bỏ ĐTVN;
Lấy():
- không có gì nếu nội dung của đoạn không được liệt kê.
Nếu nội dung được liệt kê: GetEnumerator (), một MoveNext và một Hiện tại cho mỗi mục liệt kê, liệt kê liệt kê;
Bỏ qua (): cho mỗi đoạn được liệt kê (KHÔNG phải nội dung của đoạn): GetEnumerator (), MoveNext () chunkSize lần, không có Hiện tại! Vứt bỏ điều tra viên
Nếu bạn nhìn vào những gì xảy ra với điều tra viên, bạn sẽ thấy rằng có rất nhiều cuộc gọi đến MoveNext () và chỉ gọi Current
cho các mục TSource mà bạn thực sự quyết định truy cập.
Nếu bạn lấy N Chunks có kích thước chunkSize, thì hãy gọi tới MoveNext ()
- N lần cho bất kỳ ()
- chưa có thời gian cho Take, miễn là bạn không liệt kê Chunks
- N lần chunkSize cho Skip ()
Nếu bạn quyết định chỉ liệt kê các phần tử M đầu tiên của mỗi phần được tìm nạp, thì bạn cần gọi MoveNext M lần cho mỗi phần được liệt kê.
Tổng số
MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)
Vì vậy, nếu bạn quyết định liệt kê tất cả các yếu tố của tất cả các khối:
MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once
MoveNext có nhiều công việc hay không, tùy thuộc vào loại chuỗi nguồn. Đối với danh sách và mảng, đó là một chỉ số tăng đơn giản, có thể là kiểm tra ngoài phạm vi.
Nhưng nếu IEnumerable của bạn là kết quả của truy vấn cơ sở dữ liệu, hãy đảm bảo rằng dữ liệu thực sự được vật chất hóa trên máy tính của bạn, nếu không dữ liệu sẽ được tìm nạp nhiều lần. DbContext và Dapper sẽ chuyển đúng dữ liệu sang quy trình cục bộ trước khi có thể truy cập được. Nếu bạn liệt kê trình tự tương tự nhiều lần, nó không được tìm nạp nhiều lần. Dapper trả về một đối tượng là Danh sách, DbContext nhớ rằng dữ liệu đã được tìm nạp.
Tùy thuộc vào Kho lưu trữ của bạn, liệu có nên gọi AsEnumerable () hoặc ToLists () trước khi bạn bắt đầu phân chia các mục trong Chunks