一次被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的更多相关文章

  1. 可惜Java中没有yield return

    项目中一个消息推送需求,推送的用户数几百万,用户清单很简单就是一个txt文件,是由hadoop计算出来的.格式大概如下: uid caller 123456 12345678901 789101 12 ...

  2. C#中的using和yield return混合使用

    最近写代码为了为了省事儿用了几个yield return,因为我不想New一个List<T>或者T[]对象再往里放元素,就直接返回IEnumerable<T>了.我的代码里还有 ...

  3. yield return的用法简介

    使用yield return 语句可一次返回一个元素. 迭代器的声明必须满足以下要求: 返回类型必须为 IEnumerable.IEnumerable<T>.IEnumerator 或 I ...

  4. yield return的作用

    测试1: using UnityEngine; using System.Collections; public class test1 : MonoBehaviour { // Use this f ...

  5. yield学习续:yield return迭代块在Unity3D中的应用——协程

    必读好文推荐: Unity协程(Coroutine)原理深入剖析 Unity协程(Coroutine)原理深入剖析再续 上面的文章说得太透彻,所以这里就记一下自己的学习笔记了. 首先要说明的是,协程并 ...

  6. C#中yield return用法分析

    这篇文章主要介绍了C#中yield return用法,对比使用yield return与不使用yield return的流程,更直观的分析了yield return的用法,需要的朋友可以参考下. 本文 ...

  7. C#中的yield return与Unity中的Coroutine(协程)(下)

    Unity中的Coroutine(协程) 估计熟悉Unity的人看过或者用过StartCoroutine() 假设我们在场景中有一个UGUI组件, Image: 将以下代码绑定到Image using ...

  8. C#中的yield return与Unity中的Coroutine(协程)(上)

    C#中的yield return C#语法中有个特别的关键字yield, 它是干什么用的呢? 来看看专业的解释: yield 是在迭代器块中用于向枚举数对象提供值或发出迭代结束信号.它的形式为下列之一 ...

  9. 12.C#yield return和yield break及实际应用小例(六章6.2-6.4)

    晚上好,各位.今天结合书中所讲和MSDN所查,聊下yield关键字,它是我们简化迭代器的关键. 如果你在语句中使用了yield关键字,则意味着它在其中出现的方法.运算符或get访问器是迭代器,通过使用 ...

  10. yield return 和 yield break

    //yield return 返回类型必须为 IEnumerable.IEnumerable<T>.IEnumerator 或 IEnumerator<T>. static I ...

随机推荐

  1. 32款iOS开发插件和工具介绍[效率]

    插件和工具介绍内容均收集于网络,太多了就不一一注明了,在此谢过!   1.Charles 为了调试与server端的网络通讯协议.经常须要截取网络封包来分析. Charles通过将自己设置成系统的网络 ...

  2. HDU 5188 zhx and contest(带限制条件的 01背包)

    Problem Description As one of the most powerful brushes in the world, zhx usually takes part in all ...

  3. Android学习笔记进阶十一图片动画播放(AnimationDrawable)

    大家平时见到的最多的可能就是Frame动画了,Android中当然也少不了它.它的使用更加简单,只需要创建一个 AnimationDrawabledF对象来表示Frame动画,然后通过addFrame ...

  4. mySQL函数根据经纬度计算两点距离 复制代码

    http://www.cnblogs.com/lujiulong/p/6185041.html https://my.oschina.net/u/2273085/blog/505172?p={{pag ...

  5. php图像处理(thinkphp框架有相对强大的图像处理功能)

    php图像处理(thinkphp框架有相对强大的图像处理功能) 一.总结 1.php处理图像:php处理图像需要安装外库(gd库) 2.gd库函数可以非常完美的操作图像:安装好库之后,这个库里面的函数 ...

  6. 84.setlocale

    用法示例 #include <Windows.h> #include <stdio.h> #include<locale.h> void main() { //se ...

  7. ORACLE11g R2【RAC+ASM→RAC+ASM】

    ORACLE11g R2[RAC+ASM→RAC+ASM] 本演示案例所用环境:RAC+ASM+OMF   primary standby OS Hostname node1,node2 dgnode ...

  8. Apache/Tomcat/JBOSS/Nginx区别(转)

    先说Apache和Tomcat的区别: Apache是世界使用排名第一的Web服务器软件.它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一. ...

  9. axios封装http请求

    import axios from 'axios' const HTTP_TIMEOUT = 15000; export function httpPost(url, params = {},head ...

  10. 洛谷—— P1118 [USACO06FEB]数字三角形Backward Digit Su…

    https://www.luogu.org/problem/show?pid=1118#sub 题目描述 FJ and his cows enjoy playing a mental game. Th ...