记一次被yield return坑的历程。
来源:互联网 发布:stm32 socket编程 编辑:程序博客网 时间:2024/06/06 02:57
事情的经过是这样的:
我用C#写了一个很简单的一个通过迭代生成序列的函数。
public static IEnumerable<T> Iterate<T>(this Func<T, T> f, T initVal, int length){ Checker.NullCheck(nameof(f), f); Checker.RangeCheck(nameof(length), length, 0, int.MaxValue); var current = initVal; while (--length >= 0) { yield return (current = f(current)); }}
其中NullCheck用于检查参数是否为null,如果是则抛出ArgumentNullException异常。
对应的,我写了如下单元测试代码去检测这个异常。
public void TestIterate(){ Func<int, int> f = null; Assert.Throws<ArgumentNullException>(() => f.Iterate(1, 7)); // Other tests}
但是,这个测试出乎意料的fail了。
一开始,我以为是NullCheck函数的问题,可我把NullCheck直接换成了if语句,还是通不过。
后来我在Iterate函数下断点并调试。结果调试器根本没有停在断点上,直接运行完了测试。
我以为是我测试的方法不对,所以我不断的修改测试代码,甚至还一度以为是.NET的Unit Tests出了bug。
最终,我在这个测试代码发现了问题:
Assert.Throws<ArgumentNullException>(() =>{ var seq = f.Iterate(1, 7); foreach (int ele in seq) Console.WriteLine(ele);});
当我调试这个测试时,程序停在了我之前在Iterate函数上下的断点。
于是,我在 var seq = f.Iterate(1,7); 上下断点,并逐步运行。这时我发现,当程序运行到 var seq = f.Iterate(1,7); 时并不会进入Iterate函数;而是当程序运行到foreach语句后才进入。
这就要涉及到yield return的具体工作流程。当函数代码中出现yield return,调用这个函数会直接返回一个IEnumerable<T>或IEnumerator<T>对象,并不会执行函数体的任何代码。这些代码都被封装到了返回对象的内部。它们会在你开始枚举的时候开始执行。
因此,上面两个Check并不会在函数调用时执行,而是在当你开始foreach的时候才执行。
这并不是我想要的结果。我希望在调用函数时就检查参数合法性,如果不合法便直接抛出异常。
解决这个问题有两种途径,一是把它拆成两个函数:
public static IEnumerable<T> Iterate<T>(this Func<T, T> f, T initVal, int length){ Checker.NullCheck(nameof(f), f); Checker.RangeCheck(nameof(length), length, 0, int.MaxValue); return IterateWithoutCheck(f, initVal, length);}private static IEnumerable<T> IterateWithoutCheck<T>(this Func<T, T> f, T initVal, int length){ var current = initVal; while (--length >= 0) { yield return (current = f(current)); }}
或者,你也可以将这个函数包装成一个类。
class FunctionIterator<T> : IEnumerable<T>{ private readonly Func<T, T> f; private readonly T initVal; private readonly int length; public FunctionIterator(Func<T, T> f, T initVal, int length) { Checker.NullCheck(nameof(f), f); Checker.RangeCheck(nameof(length), length, 0, int.MaxValue); this.f = f; this.initVal = initVal; this.length = length; } public IEnumerator<T> GetEnumerator() { T current = initVal; for (int i = 0; i < length; ++i) yield return (current = f(current)); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }}
- 记一次被yield return坑的历程。
- yield return 关键字的详解
- yield与return的区别
- C# yield return 的作用
- yield与return的区别
- 对yield return的理解
- 关于yield return的使用
- 记一次macbook安装PyQt4的探坑历程
- 记一次android的cookie使用历程
- yield return
- yield return
- Yield Return
- yield return
- C# 中 yield return 和 yield break 关键字的用法
- C# 中 yield return 和 yield break 关键字的用法
- C# yield return 关键字的详解
- Unity中 yield return 1 的误区
- 关于协程中yield return的一些使用
- solr 分组统计
- MySQL日志设置及查看
- 浅析VO、DTO、DO、PO的概念、区别和用处
- ♥C++宏观技巧笔记
- Unity入门操作_音频_035
- 记一次被yield return坑的历程。
- 归并排序
- ubuntu下载android源码
- 关于Javascript的严格模式
- 实现推送接收并在指定页面中处理(涉及推送、service)
- note: how userspace relates to netdevice rings
- 杨辉三角
- Mongo插入文档操作
- C语言宏定义