C# IEnumerable,IEnumerator接口

許久以前學習WinForm的時候開始解除的C#,以後便擱了好長時間。最近在學Unity的時候又重拾了C#。發現之前學過的東西也都忘了差很少了。特別在Unity中會常常用到yield關鍵字和IEnumerator接口來作一些延時循環的操做。做爲重拾C#第一步,先來複習和總結一下C#中的IEnumerableIEnumerator接口。javascript

背景

在不少編程場景中,咱們須要去遍歷(Iterate)一個集合。爲了讓這個過程簡化,不少高級語言都採用遍歷語句來進行操做,例如for...in... 或者foreach()。例如在C#中,ArrayList的實例能夠在foreach()中獲得遍歷。固然除了語言原生的一些集合類外不是全部的類的實例均可以放在遍歷語句中進行操做的。要想讓一個類的實例可以在遍歷語句中獲得遍歷,就必須按照語言的規定讓類實現某些接口或者屬性。java

原生ArrayList

在咱們開始正式進入正題前,咱們先看一下C#原生的ArrayList是怎麼樣工做的。express

using System.IO;
using System;
using System.Collections;

class Test
{
    static void Main()
    {
        ArrayList array = new ArrayList();
        array.Add(1);
        array.Add(2);
        array.Add("3");
        array.Add(4);
        foreach(object i in array){
            Console.WriteLine(i);
        }
    }
}

很簡單是吧,那咱們下面就開始闡述IEnumerableIEnumerator接口。並實現一個本身版本的ArrayList編程

開始實現本身的ArrayList

首先咱們來認識一下咱們今天的主角,IEnumerable, IEnumerable<T>, IEnumerator, IEnumberator<T>,長的真的很像呢。首先咱們一眼就能夠看出後面帶的就是有類型的(C#裏叫作generic)。generic版本和non-generic版本稍有些不一樣。做爲開始咱們先實現non-generic版吧。那就選定了咱們的男一和女一,IEnumerable, IEnumerator。瞅一眼它們的簡歷吧。官方文檔是這麼寫的:ide

IEnumerable is the base interface for all non-generic collections that can be enumerated. For the generic version of this interface see System.Collections.Generic.IEnumerable. IEnumerable contains a single method, GetEnumerator, which returns an IEnumerator. IEnumerator provides the ability to iterate through the collection by exposing a Current property and MoveNext and Reset methods.函數

IEnumerable是那些能夠被遍歷的集合中所須要實現的基礎接口,IEnumerable有一個方法GetEnumerator(),這個方法發回一個IEnumerator類型,IEnumerator包含一個Current屬性和MoveNextReset方法,經過這些屬性和方法就能夠遍歷這個集合了。因此咱們本身的ArrayList應該這麼實現:學習

public class MyArrayList: IEnumerable
{

    //some code
    
    public IEnumerator GetEnumerator()
    {
       //some code
       //return new MyEnumerator(...);
    }
}

能夠看出GetEnumerator返回一個IEnumerator類型,因此咱們就必需要去實現本身的IEnumerator類:this

public class MyEnumerator:IEnumerator
{
    
    public bool MoveNext()
    {
        //some code
    }
    
    public void Reset()
    {
        //some code
    }
    
    public object Current
    {
        get
        {
            // some code
        }
    }
}

知道了基本結構,就能夠擴展出咱們想要的ArrayList結構了,完整的代碼以下:code

using System.IO;
using System;
using System.Collections;


public class MyArrayList
{
    
    object[] data;
    int currentIndex;
    
    public MyArrayList(int length)
    {
        this.data = new object[length];
        currentIndex = 0;
    }
    
    public void Add(object s)
    {
        data[currentIndex++] = s;
    }
    
    public IEnumerator GetEnumerator()
    {
        return new MyEnumerator(data);
    }
}

public class MyEnumerator:IEnumerator
{
    
    private object[] _data;
    private int position = -1;
    
    public MyEnumerator(object[] data)
    {
        _data = data;
    }
    
    public bool MoveNext()
    {
        position++;
        return (position < _data.Length);
    }
    
    public void Reset()
    {
        position = -1;
    }
    
    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }
    
    public object Current
    {
        get
        {
            return _data[position];
        }
    }
}

class Test
{
    static void Main()
    {
        MyArrayList array = new MyArrayList(10);
        array.Add("Jack");
        array.Add("Tom");
        foreach(object i in array)
        {
            Console.WriteLine(i);
        }
    }
}

這樣一個簡單的ArrayList就實現了。還有一點要注意的就是IEnumerable接口不是必需要實現的,可是要想能遍歷,必需要實現GetEnumerator()方法。可是實現IEnumerableIEnumberator接口是個好習慣。orm

IEnumerable和IEnumerator的Generic版本

對於IEnumerable<T>IEnumerator<T>的實現,稍有些不一樣,若是咱們只是把上面代碼中的IEnumerableIEnumerator換成對應的Generic接口的話:

public class MyArrayList<T>: IEnumerable<T>
{

    //some code
    
    public IEnumerator<T> GetEnumerator()
    {
       //some code
       //return new MyEnumerator<T>(...);
    }
}
public class MyEnumerator<T>:IEnumerator<T>
{
    
    public bool MoveNext()
    {
        //some code
    }
    
    public void Reset()
    {
        //some code
    }
    
    public T Current
    {
        get
        {
            // some code
        }
    }
}

這樣編譯器會報三個錯誤:

1.MyEnumerator<T> does not implement interface member System.IDisposable.Dispose()
2.MyArrayList<T> does not implement interface member System.Collections.IEnumerable.GetEnumerator() and the best implementing candidate MyArrayList<T>.GetEnumerator() return type System.Collections.Generic.IEnumerator<T> does not match interface member return type System.Collections.IEnumerator
3.MyEnumerator<T> does not implement interface member System.Collections.IEnumerator.Current.get and the best implement ing candidate MyEnumerator<T>.Current.get return type T does not match interface member return type object

第一個錯誤告訴咱們IEnumerable<T>要實現Dispose()方法,第二個,第三個錯誤要咱們實現IEnumerable.GetEnumerator()IEnumerator.Current屬性。完整代碼以下:

using System.IO;
using System;
using System.Collections;
using System.Collections.Generic;


public class MyArrayList<T>: IEnumerable<T>
{
    
    T[] data;
    int currentIndex;
    
    public MyArrayList(int length)
    {
        this.data = new T[length];
        currentIndex = 0;
    }
    
    public void Add(T s)
    {
        data[currentIndex++] = s;
    }
    
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    
    public IEnumerator<T> GetEnumerator()
    {
        return new MyEnumerator<T>(data);
    }
}

public class MyEnumerator<T>:IEnumerator<T>
{
    
    private T[] _data;
    private int position = -1;
    
    public MyEnumerator(T[] data)
    {
        _data = data;
    }
    
    public bool MoveNext()
    {
        position++;
        return (position < _data.Length);
    }
    
    public void Reset()
    {
        position = -1;
    }
    
    public void Dispose()
    {
        //Dispose the resource
    }
    
    object IEnumerator.Current
    {
        get
        {
            return Current;    
        }
    }
    
    public T Current
    {
        get
        {
            return _data[position];
        }
    }
}

public class Test
{
    static void Main()
    {
        MyArrayList<string> array = new MyArrayList<string>(10);
        array.Add("Jack");
        array.Add("Tom");
        foreach(string str in array)
        {
            Console.WriteLine(str);
        }
    }
}

yield關鍵字

用上面的方法來實現一個能夠遍歷的類多少以爲有些麻煩,要多實現一個IEnumerator類。並且Generic版本要多實現幾個方法和屬性。yield關鍵字能夠幫咱們簡化上述的過程。yield用於生成一個遍歷類型,包含yield的方法的返回值類型必須是IEnumerable,IEnumerator,IEnumerable<T>,IEnumerator<T>其中一種。yield的通常形式是yield return <expression>。例如:

public class PowersOf2
{
    static void Main()
    {
        // Display powers of 2 up to the exponent of 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }

    public static System.Collections.Generic.IEnumerable<int> Power(int number, int exponent)
    {
        int result = 1;

        for (int i = 0; i < exponent; i++)
        {
            result = result * number;
            yield return result;
        }
    }

    // Output: 2 4 8 16 32 64 128 256
}

調用上面的Power方法,不會執行函數的主體,而是返回一個IEnumerable<int>對象,在foreach中,調用MoveNext來進行遍歷,這時函數開始執行,指導碰到yield,後面返回的對象就是Current屬性的值。下次調用MoveNext的時候,會從上個yield的地方繼續日後執行。這樣知道函數結束,遍歷也就結束了。知道這個特性後咱們就能簡化上面的代碼了:

using System.IO;
using System;
using System.Collections;
using System.Collections.Generic;


public class MyArrayList<T>: IEnumerable<T>
{
    
    T[] data;
    int currentIndex;
    
    public MyArrayList(int length)
    {
        this.data = new T[length];
        currentIndex = 0;
    }
    
    public void Add(T s)
    {
        data[currentIndex++] = s;
    }
    
    IEnumerator IEnumerable.GetEnumerator(){
        return this.GetEnumerator();
    }
    
    public IEnumerator<T> GetEnumerator()
    {
        for(int i = 0; i < data.Length; i++)
        {
            yield return data[i];
        }
    }
}

public class Test
{
    static void Main()
    {
        MyArrayList<string> array = new MyArrayList<string>(10);
        array.Add("Jack");
        array.Add("Tom");
        foreach(string str in array)
        {
            Console.WriteLine(str);
        }
    }
}

總結

對於遍歷的實現,每一個語言都有本身不一樣的實現,但卻有很大的類似處,瞭解一個語言的實現也有助於對其餘語言實現的理解。結下來會寫一下javascript中對於遍歷的實現。

相關文章
相關標籤/搜索