yield return
一次被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, 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>对象,但是并不会执行函数体的任何代码。只有当你执行其返回的或调用返回对象的GetEnumerator方法得到的IEnumerator<T>对象的MoveNext()函数时,函数才会开始执行。
因此,上面两个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的更多相关文章
- 可惜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 ...
- yield学习续:yield return迭代块在Unity3D中的应用——协程
必读好文推荐: Unity协程(Coroutine)原理深入剖析 Unity协程(Coroutine)原理深入剖析再续 上面的文章说得太透彻,所以这里就记一下自己的学习笔记了. 首先要说明的是,协程并 ...
- C#中yield return用法分析
这篇文章主要介绍了C#中yield return用法,对比使用yield return与不使用yield return的流程,更直观的分析了yield return的用法,需要的朋友可以参考下. 本文 ...
- C#中的yield return与Unity中的Coroutine(协程)(下)
Unity中的Coroutine(协程) 估计熟悉Unity的人看过或者用过StartCoroutine() 假设我们在场景中有一个UGUI组件, Image: 将以下代码绑定到Image using ...
- C#中的yield return与Unity中的Coroutine(协程)(上)
C#中的yield return C#语法中有个特别的关键字yield, 它是干什么用的呢? 来看看专业的解释: yield 是在迭代器块中用于向枚举数对象提供值或发出迭代结束信号.它的形式为下列之一 ...
- 12.C#yield return和yield break及实际应用小例(六章6.2-6.4)
晚上好,各位.今天结合书中所讲和MSDN所查,聊下yield关键字,它是我们简化迭代器的关键. 如果你在语句中使用了yield关键字,则意味着它在其中出现的方法.运算符或get访问器是迭代器,通过使用 ...
- yield return 和 yield break
//yield return 返回类型必须为 IEnumerable.IEnumerable<T>.IEnumerator 或 IEnumerator<T>. static I ...
随机推荐
- home.pl 正在促销,一些域名免费(终止于2017.4.4)
home.pl 正在促销,一些域名免费(终止于2017.4.4) home.pl 成立于1997年,是波兰顶尖的互联网服务公司.专注于域名登记,托管网站,保持电子邮件帐户等. home.pl 正在促 ...
- 矩阵乘法java代码实现
矩阵只有当左边矩阵的列数等于右边矩阵的行数时,它们才可以相乘, 乘积矩阵的行数等于左边矩阵的行数,乘积矩阵的列数等于右边矩阵的列数 即A矩阵m*n,B矩阵n*p,C矩阵m*p: package exa ...
- read()方法读取的是一个字节,为什么返回是int,而不是byte
因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111 那 ...
- js引入广告服务
var header = []; header[1] = { 'title' : '新浪开始进军微博大战', 'pic' : 'images/header1.png', 'link' : 'http: ...
- web存储方法,现成代码
1.cookie的设置与取用 function setCookie(cname,cvalue,exdays){ var d = new Date(); d.setTime(d.getTime()+(e ...
- Docker 内程序时间设置,很重要
原文:Docker 内程序时间设置,很重要 重要!!!!! 创建容器时候需要修改一个参数,设置tomcat的时区 -e TZ="Asia/Shanghai" -v /etc/loc ...
- .condarc(conda 配置文件)
Configuration - Conda documentation .condarc以点开头,一般表示 conda 应用程序的配置文件,在用户的家目录(windows:C:\\users\\use ...
- 不同jquery对象触发相同的函数 “.js-story-title,.js-mt-index-cont2”
$(document).on("click",".js-story-title,.js-mt-index-cont2",function () {}
- Vue 自定义全局消息框组件
消息弹框组件,默认3秒后自动关闭,可设置info/success/warning/error类型 效果图: 文件目录: Message.vue <template> <transit ...
- macOS 上配置 Lua
[最新版]从零开始在 macOS 上配置 Lua 开发环境 脚本语言,你可能更需要的是 Lua 不同的脚本语言有不同的特性,第一接触的脚本语言,可能会影响自己对整个脚本语言的理解和认知.我以前接触 ...