事情的经过是这样的:

我用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坑的历程。的更多相关文章

  1. yield return

    一次被yield return坑的历程.   事情的经过是这样的: 我用C#写了一个很简单的一个通过迭代生成序列的函数. public static IEnumerable<T> Iter ...

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

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

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

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

  4. C#yield return和yield break

    C#yield return和yield break 晚上好,各位.今天结合书中所讲和MSDN所查,聊下yield关键字,它是我们简化迭代器的关键. 如果你在语句中使用了yield关键字,则意味着它在 ...

  5. 可惜Java中没有yield return

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

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

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

  7. yield return的用法简介

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

  8. yield return的作用

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

  9. C#中yield return用法分析

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

随机推荐

  1. java核心机制

    Java中有两种核心机制:Java虚拟机(Java Virtual Machine).垃圾收集机制(Garbage collection) 一.核心机制之Java虚拟机 ① Java虚拟机可以理解成一 ...

  2. 操作系统,银行家算法模拟实现(Windows 环境 C++)

    计算机操作系统课设需要,写了两个下午的银行家算法(陷在bug里出不来耽误了很多时间),参考计算机操作系统(汤子瀛) 实现过程中不涉及难度较大的算法,仅根据银行家算法的思想和步骤进行实现.以下为详细步骤 ...

  3. 【css】border-image

    1. border-image 一个新css3 样式 给边框增加图片,还可以拉升 或重复图片 round 为重复 sketch 为拉升 border: 15px solid transparent; ...

  4. opnet的sink模块学习 分类: opnet 2014-05-18 10:28 161人阅读 评论(0) 收藏

    Sink模块的状态机很简单,只有INIT和DISCARD两个,非强制状态只有DISCARD用于包的销毁.Sink模块的作用就是销毁从输入流接收到的包,并且返回关于包的一系列统计量. Init的入口代码 ...

  5. ETL作业调度工具TASKCTL软件安装乱码问题解决

    前两天在安装批量作业调度软件TASKCTL4.5时,将安装介质解压出来后执行安装脚本时,一安装就出现下图乱码. 然后就度娘了一下,发现安装遇到乱码的人还挺多的,大多数解答都说TASKCTL软件里面很多 ...

  6. WordPress中函数钩子hook的作用及基本用法

    WordPress 的插件机制实际上只的就是这个 Hook 了,它中文被翻译成钩子,允许你参与 WordPress 核心的运行,是一个非常棒的东西,下面我们来详细了解一下它.钩子分类 钩子分为两种,一 ...

  7. acdsee-pro-8 英文版

    目前中文版acdsee pro 8不支持破解,只能选择英文版,然后汉化. 但是英文版并不好找,我在这里分享一下. 下载地址:http://pan.baidu.com/s/1boNZxQf 同时附上注册 ...

  8. mysql控制台出现“unknown column 'password' in 'field list'问题

    今天在windows系统上使用MySQL命令时,出现下面的"unknown column 'password' in 'field list'问题 解决办法如下,使用authenticati ...

  9. Python [目录]

    [第一篇] Python 发展历史 [第二篇] Python 优缺点 [第三篇] Python 应用场景 [第四篇] Python Hello World程序 [第五篇] Python 基础知识 [第 ...

  10. Javascript数组(1)--基本属性及方法

    数组Array是Javascript语言中非常重要的两种引用类型数据之一,另外一种为对象Object.Array的数据模型可分为两种进行存储:堆栈结构.队列结构. 昨天,确切说是前天了,去和大学同学见 ...