yield为何能够实现迭代器功能

实现foreach遍历的两种方式

使用IEnumerable 与 IEnumerator接口

  首先先来了解一下这两个接口的含义,从名字常来看,IEnumerator是枚举器的意思,IEnumerable是可枚举的意思。接着看源码:程序员

IEnumerable

public interface IEnumerable
{
    // Interfaces are not serializable
    // Returns an IEnumerator for this enumerable Object.  The enumerator provides
    // a simple way to access all the contents of a collection.
    [Pure]
    [DispId(-4)]
    IEnumerator GetEnumerator();
}
复制代码

IEnumerator

public interface IEnumerator
{
    // Interfaces are not serializable
    // Advances the enumerator to the next element of the enumeration and
    // returns a boolean indicating whether an element is available. Upon
    // creation, an enumerator is conceptually positioned before the first
    // element of the enumeration, and the first call to MoveNext 
    // brings the first element of the enumeration into view.
    // 
    bool MoveNext();

    // Returns the current element of the enumeration. The returned value is
    // undefined before the first call to MoveNext and following a
    // call to MoveNext that returned false. Multiple calls to
    // GetCurrent with no intervening calls to MoveNext 
    // will return the same object.
    // 
    Object Current {
        get; 
    }

    // Resets the enumerator to the beginning of the enumeration, starting over.
    // The preferred behavior for Reset is to return the exact same enumeration.
    // This means if you modify the underlying collection then call Reset, your
    // IEnumerator will be invalid, just as it would have been if you had called
    // MoveNext or Current.
    //
    void Reset();
}
复制代码

  IEnumerable中只有一个GetEnumerator函数,返回值是IEnumerator类型,因此实现了IEnumerable接口的类能够经过此方法获取一个IEnumerator枚举器,并经过此枚举器遍历这个类中包含的集合中的元素的功能(好比List,ArrayList,Dictionary等继承了IEnumeratble接口的类)。编程

实现代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuanFa.IEnumerableInterface
{
    class InheritIEnumerable : IEnumerable
    {
        public int[] array = new int[] { 1, 3, 4 };  
        static void Main()
        {
            InheritIEnumerable ii = new InheritIEnumerable();
            Console.WriteLine("foreach执行结果:");
            foreach (int i in ii)
            {
                Console.WriteLine("i=" + i);
            }
        }
        /// <summary>
        /// 返回IEnumerator类型的对象
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            Console.WriteLine("获取枚举器");
            return new InheritIEnumerator(array);
        }
    }
    public class InheritIEnumerator : IEnumerator
    {
        int[] array;
        int pos = -1;
        public InheritIEnumerator(int []array)
        {
            this.array = array;
        }
        public object Current
        {
            get
            {
                Console.WriteLine("获取Current");
                if (pos<array.Length)
                {
                    return array[pos];
                }
                else
                {
                    throw new InvalidOperationException();
                }
            }
        }
        public bool MoveNext()
        {
            
            if (pos<array.Length-1)
            {
                Console.WriteLine("MoveNext true");
                pos++;
                return true;
            }
            else
            {
                Console.WriteLine("MoveNext false");
                return false;
            }
        }
        public void Reset()
        {
            pos = -1;
        }
    }
}

复制代码

运行结果

  

分析

经过输出结果,能够发现,foreach在运行时会先调用InheritIEnumerable的GetIEnumerator函数获取一个InheritIEnumerator实例(枚举器实例),以后经过循环调用InheritIEnumerator的MoveNext函数,pos后移,更新Current属性,而后返回Current属性,直到MoveNext返回false。 总结一下:框架

  • GetIEnumerator()负责获取枚举器。
  • MoveNext()负责让Current获取下一个值,并判断遍历是否结束。
  • Current负责返回当前指向的值。
  • Rest()负责重置枚举器的状态(在foreach中没有用到)

这些就是IEnumerable,IEnumerator的基本工做原理。   因此foreach就至关于一下代码:ide

IEnumerable ieable = new InheritIEnumerable();
            IEnumerator ie = ieable.GetEnumerator();
while (ie.MoveNext())
{
    Console.WriteLine(ie.Current);
}
复制代码

使用yield

代码实现

class YieldFunctions
{
    public static IEnumerable<int> getNums()
    {
        yield return 1;
        yield return 0;
        yield return 3;
        yield break;
        yield return 5;
    }
    public static void Main()
    {
        foreach (int i in getNums())
        {
            Console.WriteLine(i);
        }
    }
}
复制代码
  • yield return :返回迭代器的内容
  • yield break :终止迭代
  • yield只能使用在返回类型必须为 IEnumerable、IEnumerable"T"、IEnumerator 或 IEnumerator"T"的方法、运算符、get访问器中

运行结果

关键问题--使用yield迭代的时候咱们虽然没有实现GetEnumerator()方法,也没有实现对应的IEnumerator的MoveNext(),和Current属性,可是咱们仍然能正常使用foreach,换句话说也就是yield关键字在编译过程当中会发生了什么?

YieldFunctions的IL代码

先看一下YieldFunctions类的IL代码的总体框架,主要有三部分:getNums生成的IL代码,新生成的getNums类的IL代码,Main方法的IL代码。 函数

接下来查看一下getNums方法生成的IL代码,能够返如今该方法中,建立了一个getNums类的对象,并将其做为返回值。
而后看一下生成的新的类getNums的代码:

再而后能够看一下MoveNext代码: this

总结

用yield来进行迭代的真实流程就是:spa

  • 运行getNums()函数,获取代码自动生成的类的实例(IL代码中getNums类的实例)。
  • 接着调用IL代码中GetEnumberator()函数,将获取的类本身做为迭代器开始迭代。
  • 每次运行MoveNext(),state增长1,经过switch语句可让每次调用MoveNext()的时候执行不一样部分的代码。
  • MoveNext()返回false,结束。

我的总结

  • yield关键字实际上是一种语法糖,最终仍是经过实现IEnumberable"T"、IEnumberable、IEnumberator"T"和IEnumberator接口实现的迭代功能。
  • 综上两种对foreach的实现原理,无论哪一种方式,其最终原理仍是同样的,只是体现了C#的封装性更好,对程序员更加友好,但为了更好的编程,仍是须要了解这些原理。
  • 能够说,如何可使用foreach进行遍历的类,都须要直接或间接地实现IEnumerable接口,并返回IEnumerator枚举器对象进行遍历。
相关文章
相关标签/搜索