C#用yield实现List分块 如何调整使末尾分块大小更均匀
问题说明
使用yield return实现集合分块时,指定单块最大长度chunkSize=4,待拆分列表共18个元素,原有分块逻辑输出的块长度为4、4、4、4、2,需要调整逻辑得到更均匀的分块结果4、4、4、3、3,且单块长度不超过指定的chunkSize。
原有测试代码:
List<string> testList = new List<string> { "1","2","3","4","5","6","7","8","9", "10","11","12","13","14","15","16","17","18" }; // 原代码此处缺少分号,且泛型参数与列表类型不匹配,属于笔误 int chunkSize = 4; IEnumerable<IEnumerable<string>> chunkList = ChunkBy(testList, chunkSize);
原有分块实现:
private static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(IEnumerable<TSource> source, int chunkSize) { while (source.Any()) { yield return source.Take(chunkSize); source = source.Skip(chunkSize); } }
注:原有实现存在性能隐患:每次调用
Any()、Take()、Skip()都会重新遍历源序列,如果传入的是延迟执行的迭代器/数据库查询,会产生重复查询、结果不一致的问题。
调整方案
完全可以实现均匀分块,核心逻辑为:
- 先将源序列单次遍历转为内存集合,避免重复遍历开销
- 根据总元素数、指定的单块最大长度,计算实际需要的总块数
- 计算基础块长度,以及需要多分配1个元素的块数量,保证所有块的长度差不超过1,且最大块长度不超过指定的
chunkSize
调整后的实现代码:
private static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(IEnumerable<TSource> source, int chunkSize) { // 参数校验 if (source == null) throw new ArgumentNullException(nameof(source)); if (chunkSize < 1) throw new ArgumentOutOfRangeException(nameof(chunkSize), "分块大小必须大于0"); // 单次遍历转为内存集合,避免重复遍历 var sourceList = source.ToList(); int totalCount = sourceList.Count; if (totalCount == 0) yield break; // 计算总块数:保证单块最大长度不超过chunkSize int chunkCount = (int)Math.Ceiling(totalCount / (double)chunkSize); // 计算基础块长度、需要多分配1个元素的块数 int baseChunkSize = totalCount / chunkCount; int remainder = totalCount % chunkCount; int currentIndex = 0; for (int i = 0; i < chunkCount; i++) { // 前remainder个块,每个块多分配1个元素 int currentChunkSize = baseChunkSize + (i < remainder ? 1 : 0); yield return sourceList.GetRange(currentIndex, currentChunkSize); currentIndex += currentChunkSize; } }
逻辑验证
以18个元素、chunkSize=4的场景计算:
- 总块数 = Ceiling(18/4) = 5
- 基础块长度 = 18 / 5 = 3
- 余数值 = 18 % 5 = 3
- 前3个块长度为3+1=4,后2个块长度为3,最终分块结果为
4、4、4、3、3,符合预期。
该实现同时兼容其他场景,例如10个元素、chunkSize=3时,分块结果为3、3、2、2,不会出现最后一块仅1个元素的不均匀情况。
内容的提问来源于stack exchange,提问作者Nejox
相关产品推荐
相关产品推荐

