Только что наткнулся на эту ветку, и большинство решений здесь включают добавление элементов в коллекции, эффективно материализуя каждую страницу перед ее возвратом. Это плохо по двум причинам: во-первых, если у вас большие страницы, для заполнения страницы накладные расходы, во-вторых, существуют итераторы, которые делают недействительными предыдущие записи при переходе к следующей (например, если вы оборачиваете DataReader в метод перечислителя) .
Это решение использует два метода вложенных перечислителей, чтобы избежать необходимости кэшировать элементы во временные коллекции. Поскольку внешние и внутренние итераторы проходят один и тот же перечислитель, они обязательно используют один и тот же перечислитель, поэтому важно не продвигать внешний, пока вы не закончили обработку текущей страницы. Тем не менее, если вы решите не выполнять итерацию по всей текущей странице, при переходе на следующую страницу это решение автоматически выполнит итерацию вперед до границы страницы.
using System.Collections.Generic;
public static class EnumerableExtensions
{
/// <summary>
/// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="enumerable">The source enumerable</param>
/// <param name="pageSize">The number of elements to return in each page</param>
/// <returns></returns>
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
{
var enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
var indexWithinPage = new IntByRef { Value = 0 };
yield return SubPartition(enumerator, pageSize, indexWithinPage);
// Continue iterating through any remaining items in the page, to align with the start of the next page
for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
{
if (!enumerator.MoveNext())
{
yield break;
}
}
}
}
private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
{
for (; index.Value < pageSize; index.Value++)
{
yield return enumerator.Current;
if (!enumerator.MoveNext())
{
yield break;
}
}
}
private class IntByRef
{
public int Value { get; set; }
}
}