.NET Framework - yield (集合遍历、IEnumerable、IEnumerator)

创建时间:
2014-06-16 10:12
最近更新:
2018-10-07 01:18

Glosarry

  • enumerable adj. 可点数的,可列举的
  • enumerator n. 计数者,计数员;人口普查员
  • yield vt. 屈服,投降;生产;获利;不再反对 vi. 放弃,屈服;生利;退让,退位 n. 产量,产额;投资的收益;屈服,击穿;产品 -- TonyNote: 作为 C#、JavaScript、Python 等编程语言的关键字,其词义应该是 生产

Resource - microsoft.com

  1. yield (C# 参考) - Using yield to define an iterator removes the need for an explicit extra class (the class that holds the state for an enumeration, see IEnumerator<T> for an example) when you implement the IEnumerable and IEnumerator pattern for a custom collection type (当你 为 一个自定义集合类型 实现 IEnumerable 与 IEnumerator 模式 时,用 yield 定义 迭代器 消除了对 一个显式的额外的类 的需要 (这个类 保存 枚举状态)).
  2. Iterators - 迭代器方法有一个重要限制: 在同一方法中不能同时使用 return 语句和 yield return 语句。
  1. .NET 本质论 - 了解 C# foreach 的内部工作原理和使用 yield 的自定义迭代器
  2. .NET 本质论 - 使用 Yield 的自定义迭代器

Resource - C#

  1. C# 集合-列举 (enumeration)
  2. C# 中遍历各类数据集合的方法
  3. IEnumerator 与 IEnumerable 接口区别
  4. 浅谈 yield

Resource - JavaScript - mozilla.org

  1. yield 关键字
  2. yield 关键字
  3. yield* 表达式
  4. yield* 表达式

Resource - JavaScript

  1. Generator 函数的语法 - 阮一峰 ECMAScript 6 入门
  2. 用 ES6 Generator 替代回调函数 - 取号机也许是对 generator 的一个绝佳的比喻。你可以通过取一张票来向机器请求一个号码。你接收了你的号码,但是机器不会自动为你提供下一个。换句话说,取票机 "暂停",直到有人请求另一个号码,此时它才会向后运行。
  3. 深入解析 ES6: Generator - 从技术层面上讲,每当 Generator 函数执行遇到 yield 表达式时,函数的栈帧 (本地变量,函数参数,临时值 和 当前执行的位置),就从堆栈移除,但是 Generator 对象保留了对该栈帧的引用,所以下次调用 .next() 方法时,就可以恢复并继续执行。 值得提醒的是 Generator 并不是多线程。在支持多线程的语言中,同一时间可以执行多段代码,并伴随着执行资源的竞争,执行结果的不确定性和较好的性能。而 Generator 函数并不是这样,当一个 Generator 函数执行时,它与其调用者都在同一线程中执行,每次执行顺序都是确定的,有序的,并且执行顺序不会发生改变。与线程不同,Generator 函数可以在内部的 yield 的标志点暂停执行。

Resource - Python

  1. Python yield 与实现

Note

如果通过 yield return 返回集合元素,将导致 "延后执行" —— 对返回的集合进行迭代时才执行。

在使用 yield 关键字的时候,返回类型必须是

  • IEnumerable
  • IEnumerable<T>
  • IEnumerator
  • IEnumerator<T>

说明: 一个包含 yield 语句的方法或属性都可被看作一个迭代块,一个迭代块必须定义一个返回一个 IEnumerator 或者 IEnumerable 接口,这模块可能包含多个 yield 语句或 yield 跳出语句,但是只返回 (return) 是不被允许的。

与其说 foreach 遍历 List,不如说 foreach 遍历的是 List 中的 GetEnumerator() 返回的枚举器,注意这个枚举器实现了 IEnumerator 接口,(注意:IEnumerable 接口标识某个类具备被遍历的能力,而 IEnumerator 接口则使某个类真正具备这个能力)。而当 foreach 对 List 进行循环遍历时,每个循环就是通过 yield 来分隔的。

foreach 其实遍历的是 List 中的 GetEnumerator() 返回的枚举器 enumertor,而这个枚举器所实现的接口 IEnumerator 中定义了只读的 Current 属性 (用来获取枚举器当前的所指的集合中的元素)、MoveNext 方法 (将枚举器推进到集合中的下一个元素,返回值代表是否到了集合末尾)、Reset方法 (使枚举器指到集合第一个元素之前,也就是重置枚举器),明白了这些,我们就可以通过 while 语法来实现跟 foreach 一样的功能了,而上文中的 yield 关键字浅显的理解就是用来划分要遍历的集合中的每个元素的。

Example of GetEnumerator

public IEnumerator GetEnumerator()
{
    for(int i = 0; i < _Days.Length; i++) {
        yield return _Days[i];
    }
}

public class ClassA {
    public IEnumerator GetEnumerator() {
        yield return "Hello";
        yield return "World";
    }
}

Example of yield break

public static IEnumerable<T> Function<T>(IEnumerable<T> values)
{
    if(values.Count() < 1) {
        yield break;
    }
    foreach(var item in values) {
        yield return item;
    }
}

Example

using System;
using System.Collections;

namespace Net451Console
{

    class Program
    {

        static void Main(string[] args)
        {
            string[] days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
            var daysList = new DaysList_For<string>(days);

            //迭代方式一:
            foreach(string day in daysList) {
                Console.WriteLine(day);
            }

            //迭代方式二:
            IEnumerator enumertor = daysList.GetEnumerator();
            while(enumertor.MoveNext()) {
                System.Console.WriteLine(enumertor.Current);
            }
        }

    }

    public class DaysList_For<T> : IEnumerable
    {

        private T[] _Days;

        /// <summary>
        /// Constructor.
        /// </summary>
        public DaysList_For(T[] days)
        {
            _Days = days;
        }

        public IEnumerator GetEnumerator()
        {
            for(int i = 0; i < _Days.Length; i++) {
                yield return _Days[i];
            }
        }

    }

    public class DaysList_MoreReturn<T> : IEnumerable
    {

        private T[] _Days;

        /// <summary>
        /// Constructor.
        /// </summary>
        public DaysList_MoreReturn(T[] days)
        {
            _Days = days;
        }

        public IEnumerator GetEnumerator()
        {
            yield return _Days[0];
            yield return _Days[1];
            yield return _Days[2];
            yield return _Days[3];
            yield return _Days[4];
            yield return _Days[5];
            yield return _Days[6];
        }

    }

    public class DaysList_Goto<T> : IEnumerable
    {

        private T[] _Days;

        /// <summary>
        /// Constructor.
        /// </summary>
        public DaysList_Goto(T[] days)
        {
            _Days = days;
        }

        //for 只是一个非常普遍的语法糖,在没有 for 之前都是 goto,所以可以复古为以下形式:
        public IEnumerator GetEnumerator()
        {
            int index;

            //Reset():
            {
                index = 0;
            }

        //标签之前都属于 Reset():
        label: {
                //Current
                //如果这里还有语句,都属于Current
                yield return _Days[index];
            }

            //yield 之后到 goto 之前都属于 MoveNext()
            {
                if(index < _Days.Length) {
                    index++;
                    goto label;
                }
            }
        }

    }

    public class DaysList_OtherInterface<T> : IEnumerator
    {

        private T[] _Days;
        private int _Index;
        public DaysList_OtherInterface(T[] days)
        {
            _Days = days;
        }
        public object Current
        {
            get { return _Days[_Index]; }
        }
        public bool MoveNext()
        {
            if(_Index < _Days.Length) {
                _Index++;
                return true;
            }
            else {
                return false;
            }
        }
        public void Reset()
        {
            _Index = 0;
        }

    }

}