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枚舉器對象進行遍歷。
相關文章
相關標籤/搜索