我相信您可以使用许多使用Take
和的查询Skip
,但这些查询会在原始列表上添加过多的迭代。
相反,我认为您应该创建自己的迭代器,如下所示:
public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
然后,您可以调用此函数,并且启用了LINQ,因此可以对所得序列执行其他操作。
根据Sam的回答,我觉得有一个更简单的方法可以做到:
- 再次遍历列表(我本来没有这么做)
- 在释放大块之前将这些项目分组实现(对于大块项目,将存在内存问题)
- Sam发布的所有代码
就是说,这是另一遍,我在扩展方法中将其编码IEnumerable<T>
为Chunk
:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException("source");
if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
没什么奇怪的,只是基本的错误检查。
转到ChunkInternal
:
private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
this IEnumerable<T> source, int chunkSize)
{
// Validate parameters.
Debug.Assert(source != null);
Debug.Assert(chunkSize > 0);
// Get the enumerator. Dispose of when done.
using (IEnumerator<T> enumerator = source.GetEnumerator())
do
{
// Move to the next element. If there's nothing left
// then get out.
if (!enumerator.MoveNext()) yield break;
// Return the chunked sequence.
yield return ChunkSequence(enumerator, chunkSize);
} while (true);
}
基本上,它会获取IEnumerator<T>
和手动遍历每个项目。它检查是否当前有任何项目要枚举。枚举每个块之后,如果没有剩余的项目,它就会爆发。
一旦检测到序列中有项目,就将内部IEnumerable<T>
实现的责任委托给ChunkSequence
:
private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator,
int chunkSize)
{
// Validate parameters.
Debug.Assert(enumerator != null);
Debug.Assert(chunkSize > 0);
// The count.
int count = 0;
// There is at least one item. Yield and then continue.
do
{
// Yield the item.
yield return enumerator.Current;
} while (++count < chunkSize && enumerator.MoveNext());
}
由于MoveNext
已经在IEnumerator<T>
传递给时调用了ChunkSequence
它,因此它会产生返回的项目Current
,然后递增计数,确保chunkSize
每次迭代后都不要返回超过项目的数量,并移至序列中的下一个项目(但如果产生的项目超过了块大小)。
如果没有剩余的项目,则该InternalChunk
方法将在外循环中进行另一遍传递,但是根据文档(强调我的MoveNext
观点),第二次调用该方法仍将返回false :
如果MoveNext通过了集合的末尾,则枚举数将位于集合中最后一个元素之后,并且MoveNext返回false。当枚举器位于此位置时,对MoveNext的后续调用也将返回false,直到调用Reset。
此时,循环将中断,序列序列将终止。
这是一个简单的测试:
static void Main()
{
string s = "agewpsqfxyimc";
int count = 0;
// Group by three.
foreach (IEnumerable<char> g in s.Chunk(3))
{
// Print out the group.
Console.Write("Group: {0} - ", ++count);
// Print the items.
foreach (char c in g)
{
// Print the item.
Console.Write(c + ", ");
}
// Finish the line.
Console.WriteLine();
}
}
输出:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
一个重要的注意事项,如果您不耗尽整个子序列或在父序列的任何位置中断都不会起作用。这是一个重要的警告,但是如果您的用例是您将消耗序列序列中的每个元素,那么这将对您有用。
此外,如果您按顺序操作,它将做奇怪的事情,就像Sam在某一时刻所做的那样。