c#列舉和迭代器

列舉 - Enumeration

迭代器是一個值序列(集合)上的一個只讀且只向前移動的遊標。迭代器要麼實現了IEnumerator接口,要麼實現了IEnumerator<T>接口。ide

從技術的角度看,若是一個對象有MoveNext方法以及Current屬性,那麼咱們就能夠將其看做一個迭代器。this

咱們可使用foreach語句去迭代一個可列舉對象。可迭代的對象其實就是一個序列的邏輯體現。可列舉的對象不但自身就是一個遊標,並且它還能夠生成一個遊標迭代本身。所以,可列舉的對象有兩個特性spa

  • 實現IEnumerator接口,或實現IEnumerator<T>接口
  • 有一個方法GetEnumerator,該方法返回一個迭代器

列舉模式:翻譯

class Enumerator
{
    public IteratorVariableType Current {get {...}}   
    public bool MoveNext() {...}
}

class Enumerable
{
    public Enumerator GetEnumerator() {...}
}
Enumeration pattern

爲了更好的理解上面的機率和模式,咱們來看下面的兩個例子3d

foreach (char c in "CSharp")
    Console.WriteLine(c);
Sample 1
using (var enumerator = "CSharp".GetEnumerator())
{
    while (enumerator.MoveNext()) {
        Console.WriteLine(enumerator.Current);
    }
}
Sample 2

Sample1採起了foreach這樣的高級方式去迭代字符串(由於字符串類實現了CharEnumerator);而Sample2則使用了底層的方式完成對字符串的迭代。 對於Sample咱們使用了using語句,這是由於CharEnumerator實現了IDisposable接口,下面的代碼顯示了CharEnumrator的大部分代碼(來自微軟官方)code

public sealed class CharEnumerator : IEnumerator, IDisposable
{
    private String str;
    private int index;
    private char currentElement;

    internal CharEnumerator(String str)
    {
        this.str = str;
        this.index = -1;
    }


    public bool MoveNext()
    {
        if (index < (str.Length - 1))
        {
            index++;
            currentElement = str[index];
            return true;
        }
        else
            index = str.Length;
        return false;

    }

    public void Dispose()
    {
        if (str != null)
            index = str.Length;
        str = null;
    }


    public char Current
    {
        get
        {
            return currentElement;
        }
    }

    public void Reset()
    {
        currentElement = (char)0;
        index = -1;
    }
}
CharEnumerator

初始化集合

咱們可以使用一行語句實例一個可列舉的對象。好比:IList<Int> list = new List<int>{1,2,3};編譯時,編譯器會自動翻譯爲:對象

IList<Int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
Translated Code

這是由於該列舉對象實現了IEnumerable接口,並且還包含了Add方法。
爲了驗證此點,咱們能夠經過查看IL代碼的方式來確認:blog

IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
  IL_0006:  stloc.1
  IL_0007:  ldloc.1
  IL_0008:  ldc.i4.1
  IL_0009:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
  IL_000e:  nop
  IL_000f:  ldloc.1
  IL_0010:  ldc.i4.2
  IL_0011:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
  IL_0016:  nop
  IL_0017:  ldloc.1
  IL_0018:  ldc.i4.3
  IL_0019:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
  IL_001e:  nop
  IL_001f:  ldloc.1
  IL_0020:  stloc.0
  IL_0021:  call       string [mscorlib]System.Console::ReadLine()
IL Code

迭代器 - Iterator

既然foreach可應用於列舉,那麼一個列舉能夠生成一個迭代器。很繞口很困惑是吧,咱們先來看下面的例子:使用迭代器返回斐波納契數列接口

static IEnumerable<int> Fibonacci(int number)
{ 
    for(int i=0, prevFib=1, curFib=1;i<number;i++)
    {
        yield return prevFib;
        int newFib = prevFib + curFib;
        prevFib = curFib;
        curFib = newFib;
    }
}

// test
static void Main(string[] args)
{
    foreach (int f in Fibonacci(10))
        Console.WriteLine(f);

    Console.ReadLine();
}
Fibonacci

請注意,在上面的代碼中,咱們使用了yield return。那麼它和return有什麼區別呢?
return:從方法中返回一個值
yield return:從當前的迭代器中生成下一個元素。yield語句每執行一次,程序的控制權就退還給調用者,而被調用者的狀態仍然保留,這就使得方法在調用者列舉下一個元素的時候能繼續執行。被調用者的狀態的生命週期取決於列舉,正由於如此,當調用者完成列舉後,被調用者的狀態得以釋放。生命週期

迭代器語法

迭代器能夠是包含了一個或多個yield語句的方法、屬性、或所引器。迭代器必須返回下面四個類型之一:IEnumerable, IEnumerable<T>, IEnumerator, IEnumerator<T>

再繼續下一步以前,咱們看一下IEnumerable接口和IEnumerator的定義

public interface IEnumerator
{
    bool MoveNext();
    Object Current {get; }
    void Reset();
}

public interface IEnumerable
{  
    IEnumerator GetEnumerator();
}
IEnumerator & IEnumerable

迭代器與列舉有不同的語法,在於迭代器須要返回可列舉的接口或者列舉器接口。

建立序列

迭代器能夠進一步用於建立迭代。爲了證明這點,咱們能夠擴展咱們斐波納契數列例子

static IEnumerable<int> Fibonacci(int number)
{ 
    for(int i=0, prevFib=1, curFib=1;i<number;i++)
    {
        yield return prevFib;
        int newFib = prevFib + curFib;
        prevFib = curFib;
        curFib = newFib;
    }
}

static IEnumerable<int> EvenNumbers(IEnumerable<int> sequence)
{
    foreach (int x in sequence)
        if (x % 2 == 0)
            yield return x;
}

static void Main(string[] args)
{
    foreach (int f in EvenNumbers(Fibonacci(8)))
        Console.WriteLine(f);

    Console.ReadLine();
}
Composable Iterator

請注意,直到Fibonacci方法所產生的數列的MoveNext()方法被調用時(執行foreach循環,會隱式地調用IEnumerator的MoveNext方法),纔會判斷該元素是否爲偶數。

迭代器能夠進一步用於建立迭代大量應用於LINQ。

相關文章
相關標籤/搜索