事情的经过是这样的:

我用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. PHPsthdy+xdebug

    PHPsthdy下载后查看phpinfo后会发现没有xdebug这一项: 1.phpStudy集成了XDebug扩展,所以不用单独下载XDebug. 2.打开XDebug扩展:右击PHPstudy的图 ...

  2. 前端数据存储方案集合(cookie localStorage等)以及详解 (一)

    客户端.前端 存储 一. 起 因 首先解释下为什么想来写这个关于前端存储的问题,因为最近在做小程序相关的内容.但是,在开发过程中,我们难免会遇到 token 存储. 代码缓存. 图片存储等等. 以及可 ...

  3. nodeJS之URL

    前面的话 在HTTP部分,详细介绍了URL的相关知识.而nodejs中的url模块提供了一些实用函数,用于URL处理与解析.本文将详细介绍nodeJS中的URL URL对象 解析 URL 对象有以下内 ...

  4. js验证15位或18位身份证

    本篇文章是本人在网上搜集了一些验证,然后又个人进行一定修改的关于身份证的验证,欢迎修改指正..... function IdCardValidateRule(idCard) { var tip;    ...

  5. 日本語の文法⇒ day1 限る型…意向形の紹介

    1)限る型 ① 不仅仅 名+に限らず この漫画は子供に限らず.大人にも人気が高い. 最近は女性に限らず.男もダイエットに励む人が多い. ②仅仅....名+限って 当店は.本日に限って半額をセールを実施 ...

  6. 解决谷歌浏览器 chrome解决跨域(CORS)问题---chrome插件

    chrome中跨域问题解决方案 插件解决, 插件地址  https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfb ...

  7. Section 1.1 Your Ride Is Here

    Your Ride Is Here It is a well-known fact that behind every good comet is a UFO. TheseUFOs often com ...

  8. 70. Climbing Stairs【leetcode】递归,动态规划,java,算法

    You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb ...

  9. 175. Combine Two Tables【LeetCode】-LEFT JON 和RIGHT JOIN,两张表关联查询-java -sql入门

    Table: Person +-------------+---------+ | Column Name | Type | +-------------+---------+ | PersonId ...

  10. IAT重定向之修复

    .. 仅允许非商业转载,转载请注明出处