C#如何单次操作实现TakeWhile效果同时返回匹配序列和剩余序列?
解决方案
现有实现的问题
- 当前
TakeWhile+Skip(a.Count)的写法会至少2次枚举源序列,若源是未物化的IEnumerable<T>(比如流式读取的大集合、数据库查询返回的可枚举对象),会重复执行查询逻辑,性能损耗严重,甚至部分向前只读的枚举器会直接抛出异常。 - 原业务代码中用
Count() > 0判断集合非空会全量遍历序列计数,对大集合来说额外开销极高。
单次遍历实现方案
自定义LINQ扩展方法,单次遍历即可同时返回匹配前缀和剩余序列:
public static class EnumerableExtensions { public static (List<T> Matched, IEnumerable<T> Remaining) SplitTakeWhile<T>( this IEnumerable<T> source, Func<T, bool> predicate) { if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); var matched = new List<T>(); using var enumerator = source.GetEnumerator(); // 遍历匹配前缀部分 while (enumerator.MoveNext()) { var current = enumerator.Current; if (predicate(current)) { matched.Add(current); } else { // 遇到不匹配项,返回已匹配集合 + 剩余序列迭代器 return (matched, GetRemaining(enumerator)); } } // 所有元素都满足匹配条件,剩余序列为空 return (matched, Enumerable.Empty<T>()); } // 将枚举器剩余元素转为惰性迭代的IEnumerable private static IEnumerable<T> GetRemaining<T>(IEnumerator<T> enumerator) { do { yield return enumerator.Current; } while (enumerator.MoveNext()); } }
优化后的业务代码
while (data.Any()) { var firstItem = data.First(); int y = firstItem.Year; var (year, remaining) = data.SplitTakeWhile(c => c.Year == y); data = remaining; Console.WriteLine($"{year.Count} items in {y}"); }
优化点说明
- 全程仅枚举1次源序列,无重复遍历开销,适配大集合流式处理场景
- 用
Any()替代Count() > 0判断非空,仅需读取第一个元素即可返回结果,大幅降低大集合下的判断开销 - 匹配结果直接存储在List中,调用
Count属性是O(1)操作,无额外计算成本
内容的提问来源于stack exchange,提问作者Mr. Boy
相关产品推荐
相关产品推荐

