C#扩展方法如何不枚举无限序列即可完成参数合法性校验
结论
需求可以实现,但你当前的代码无法满足要求,需要调整扩展方法的结构。
问题原因
C#中包含yield return语法的方法属于迭代器方法,这类方法的执行是延迟的:
- 直接调用方法时不会执行方法体内的任何代码,仅返回一个实现了
IEnumerable的状态机对象 - 只有当你主动枚举返回值(调用
MoveNext())时,才会真正执行方法内部的逻辑
你当前把参数校验的if逻辑和迭代器逻辑写在同一个方法里,所以调用infiniteSeq.Smooth(-1)的时候不会触发参数校验,测试会失败;如果强制枚举返回值又会触发对infiniteSeq的遍历,违反你的需求。
修复方案
把参数校验和迭代器逻辑拆分为两个独立的方法,参数校验放在外层普通方法中立刻执行,迭代器逻辑放到内层嵌套的迭代器方法中即可,修改后的代码如下:
public static IEnumerable<double> Smooth(this IEnumerable<double> s, int N) { // 参数校验放在外层普通方法,调用时立刻执行 if (N < 0) throw new ArgumentOutOfRangeException(nameof(N), "Is negative"); if (s is null) throw new ArgumentNullException(nameof(s), "is null"); // 迭代器逻辑放到内层方法,只有枚举的时候才会执行 return SmoothCore(s, N); } private static IEnumerable<double> SmoothCore(IEnumerable<double> s, int N) { while (true) { List<double> buffer = new List<double>(); using (var seqEnumerator = s.GetEnumerator()) { int index = 0; while (seqEnumerator.MoveNext()) { buffer.Add(seqEnumerator.Current); //Enough items to start Smoothing if (buffer.Count >= N*2) { List<double> elementForAvg; try { int startIndex = (index-N<0) ? 0 : index-N; int endIndex = index+N; elementForAvg = buffer.GetRange(startIndex,endIndex); } catch (Exception e) { if (e is ArgumentException) throw new FiniteSourceException(); throw; } yield return AvgCalculator(elementForAvg); index++; } } throw new FiniteSourceException(); } } }
效果说明
修改后你原有的测试代码可以直接通过:
Assert.That(()=> infiniteSeq.Smooth(-1), Throws.TypeOf<ArgumentOutOfRangeException>());
调用Smooth方法时会立刻执行参数校验抛出异常,完全不会进入迭代器逻辑,也不会对infiniteSeq做任何枚举操作,符合你的全部要求。
内容的提问来源于stack exchange,提问作者Lidenbrock
相关产品推荐
相关产品推荐

