记一次被yield return坑的历程。
事情的经过是这样的:
我用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, , int.MaxValue); var current = initVal;
while (--length >= )
{
yield return (current = f(current));
}
}
其中NullCheck用于检查参数是否为null,如果是则抛出ArgumentNullException异常。
对应的,我写了如下单元测试代码去检测这个异常。
public void TestIterate()
{
Func<int, int> f = null;
Assert.Throws<ArgumentNullException>(() => f.Iterate(, )); // Other tests
}
但是,这个测试出乎意料的fail了。
一开始,我以为是NullCheck函数的问题,可我把NullCheck直接换成了if语句,还是通不过。
后来我在Iterate函数下断点并调试。结果调试器根本没有停在断点上,直接运行完了测试。
我以为是我测试的方法不对,所以我不断的修改测试代码,甚至还一度以为是.NET的Unit Tests出了bug。
最终,我在这个测试代码发现了问题:
Assert.Throws<ArgumentNullException>(() =>
{
var seq = f.Iterate(, );
foreach (int ele in seq)
Console.WriteLine(ele);
});
当我调试这个测试时,程序停在了我之前在Iterate函数上下的断点。
于是,我在 var seq = f.Iterate(, ); 上下断点,并逐步运行。这时我发现,当程序运行到 var seq = f.Iterate(, ); 时并不会进入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, , 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 >= )
{
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, , int.MaxValue); this.f = f;
this.initVal = initVal;
this.length = length;
} public IEnumerator<T> GetEnumerator()
{
T current = initVal; for (int i = ; i < length; ++i)
yield return (current = f(current));
} System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
记一次被yield return坑的历程。的更多相关文章
- yield return
一次被yield return坑的历程. 事情的经过是这样的: 我用C#写了一个很简单的一个通过迭代生成序列的函数. public static IEnumerable<T> Iter ...
- yield学习续:yield return迭代块在Unity3D中的应用——协程
必读好文推荐: Unity协程(Coroutine)原理深入剖析 Unity协程(Coroutine)原理深入剖析再续 上面的文章说得太透彻,所以这里就记一下自己的学习笔记了. 首先要说明的是,协程并 ...
- 12.C#yield return和yield break及实际应用小例(六章6.2-6.4)
晚上好,各位.今天结合书中所讲和MSDN所查,聊下yield关键字,它是我们简化迭代器的关键. 如果你在语句中使用了yield关键字,则意味着它在其中出现的方法.运算符或get访问器是迭代器,通过使用 ...
- C#yield return和yield break
C#yield return和yield break 晚上好,各位.今天结合书中所讲和MSDN所查,聊下yield关键字,它是我们简化迭代器的关键. 如果你在语句中使用了yield关键字,则意味着它在 ...
- 可惜Java中没有yield return
项目中一个消息推送需求,推送的用户数几百万,用户清单很简单就是一个txt文件,是由hadoop计算出来的.格式大概如下: uid caller 123456 12345678901 789101 12 ...
- C#中的using和yield return混合使用
最近写代码为了为了省事儿用了几个yield return,因为我不想New一个List<T>或者T[]对象再往里放元素,就直接返回IEnumerable<T>了.我的代码里还有 ...
- yield return的用法简介
使用yield return 语句可一次返回一个元素. 迭代器的声明必须满足以下要求: 返回类型必须为 IEnumerable.IEnumerable<T>.IEnumerator 或 I ...
- yield return的作用
测试1: using UnityEngine; using System.Collections; public class test1 : MonoBehaviour { // Use this f ...
- C#中yield return用法分析
这篇文章主要介绍了C#中yield return用法,对比使用yield return与不使用yield return的流程,更直观的分析了yield return的用法,需要的朋友可以参考下. 本文 ...
随机推荐
- 【学习笔记】C# 抽象类
抽象类 有时设计类仅仅为了表达抽象的概念,不与具体的事物相联系,只作为其派生类的基类使用,用来描述所有子类的共同特性,这时我们可以使用抽象类 抽象类不能被实例化,抽象类可以包含抽象方法 抽象方法 抽象 ...
- 如何得到iPhone手机的UUID
背景 测试ad-hoc打包方式打出来的包,必须在证书里面配置手机的uuid才能安装. 这样就需要获取iPhone手机的uuid来进行证书配置 一般来说iPhone手机可以安装通过Apple发布的软件. ...
- readelf相关命令
-a --all 显示全部信息,等价于 -h -l -S -s -r -d -V -A -I. -h --file-header 显示elf文件开始的文件头信息. -l --program-heade ...
- 一小时学会ECMAScript6新特性
ECMAScript 简介 简称es,是一套标准,javascript就是使用这套标准的语言.主流的浏览器使用的是ECAMScript5,ECAMScript6(ECAMScript2015)是一涛新 ...
- POJ 2393 Yogurt factory 贪心
Description The cows have purchased a yogurt factory that makes world-famous Yucky Yogurt. Over the ...
- 推荐60个jQuery插件(转)
jQuery插件jQuery Spin Button自定义文本框数值自增或自减 jQuery插件JQuery Pager分页器实现javascript分页功能 jQuery插件FontSizer实现J ...
- Python 爬虫抓取代理IP,并检测联通性
帮朋友抓了一些代理IP,并根据测试联的通性,放在了不通的文件夹下.特将源码分享 注意: 1,环境Python3.5 2,安装BeautifulSoup4 requests 代码如下: 1 2 3 4 ...
- TP3.2写上下篇
笔记笔记笔记记笔记!! ************************************************************************** 要实现上一篇跟下一篇,需要 ...
- 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度。 2)输出字符串中第一个出现字母a的位置。 3)在字符串的第3个字符后面插入子串“hello”,输出新字符串。 4)将字符串“hello”替换为“me”,输出新字符串。 5)以字符“m”为分隔符,将字符串分离,并输出分离后的字符串。 */
namespace test4 {/* 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度. 2)输出字符串中第一个出现字母a的位置. 3)在字符串的第3个字符 ...
- 一步一步学MySQL-日志文件
错误日志 错误日志不用多说,记录了mysql运行过程中的错误信息,当出现问题时,我们可以通过错误日志查找线索. 慢查询日志 可以通过参数long_query_time来设置时间,当sql语句执行超过指 ...