您可以使用许多使用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's answer,我觉得有一种更简单的方法可以做到这一点:
- 再次遍历列表(我最初没有这样做)
- 在释放块之前按组实现项目(对于大块项目,会出现内存问题)
- 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(nameof(source));
if (chunkSize <= 0) throw new ArgumentOutOfRangeException(nameof(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> 并手动迭代每个项目。它检查当前是否有要枚举的项目。每一个chunk被枚举完后,如果没有剩下的item,就会爆发出来。
一旦检测到序列中有项目,它会将内部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 已经在传递给ChunkSequence 的IEnumerator<T> 上调用,它产生Current 返回的项目,然后增加计数,确保返回的项目永远不会超过chunkSize 并移动到每次迭代后序列中的下一项(但如果产生的项数超过块大小,则会短路)。
如果没有剩余项目,那么InternalChunk 方法将在外循环中再进行一次传递,但是当第二次调用MoveNext 时,它仍然会返回false,as per the documentation(强调我的):
如果 MoveNext 超过集合的末尾,则枚举数为
定位在集合中的最后一个元素和 MoveNext 之后
返回假。 当枚举器在这个位置时,后续
在调用 Reset 之前,对 MoveNext 的调用也会返回 false。
此时,循环将中断,序列序列将终止。
这是一个简单的测试:
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's did at one point一样。