1. 程式人生 > >從迭代器到Unity的Coroutines

從迭代器到Unity的Coroutines

Iterator迭代器和foreach迴圈

Iterator迭代器是設計模式中較為常見的一種,它的意圖是提供一種方法順序訪問一個聚合物件中的各個元素而不暴露物件的內部表示。一般而言,Current Item返回當前正在訪問的元素,從First開始,通過Next操作逐步執行到最後一個元素從而對容器完成一次訪問。

在C#中提供的foreach就是通過迭代器訪問容器內元素的一種操作。要使用foreach,容器必須是IEnumerable的實現。

關於IEnumerable,通過MSDN上關於IEnumerable 的說明可以瞭解到實現這個介面只需要實現

IEnumerator GetEnumerator();
這個函式用於返回一個遍歷容器的迭代器。這裡的IEnumerator是C#中迭代器的抽象介面,這個介面只有兩個方法和一個屬性
object Current { get; }
bool MoveNext();
void Reset();

和迭代器的一般模式無異,只不過增加了一個Reset操作。

這裡需要注意的是,IEnumerable和IEnumerator都有對應的泛型版本IEnumerable<T>和IEnumerator<T>。
IEnumerable<T>的GetEnumerator()返回的是IEnumerator<T>。IEnumerator<T>的Current的返回型別是T而不是object

T Current { get; }

直接返回對應型別而不是object可以避免型別轉換帶來的效能問題,對引用型別來說這部分的效能問題不大,但對於值型別,這裡涉及到了Boxing和Unboxing,在較大的操作中帶來的效能問題還是相當可觀的,所以儘量實現泛型版本的 IEnumerable和IEnumerator。具體的實現細節可以參考MSDN

所以至此我們可以簡單的對foreach迴圈有一個認識,它其實類似於如下的while迴圈

var tmp = obj.GetEnumerator();
while( tmp.MoveNext() ) {
	doSomething( temp.current);
}

Yield

yield實質是語法糖,它讓程式設計師能夠更方便的去使用迭代器。通過yield,你可以直接使用迭代器操作而不需要去實現 IEnumerable和IEnumerator,也不需要一個臨時的Collection來完成迭代。

yield有兩種格式的宣告

yield return <expression>;  
yield break;

通過一個例子來了解yield的工作流程

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
}

每一次foreach的迴圈中都會呼叫迭代器方法,當yield return被執行到時,表示式的值會被返回,同時當前的函式的上下文資訊被儲存下來。下一次迴圈執行之時會重新從上一次停止的位置繼續執行。你也可以使用yield break來終止迭代過程。

上面的例子中,第一次Power執行到yield return result返回2時,Power方法會被暫時停止,int result的值會被保留下來,然後返回到foreach迴圈體中,執行迴圈體中的程式碼,執行完成後再回到Power的迭代中,此時Power從剛才yield return的地方再次開始執行返回4,然後再回到迴圈體中,依次執行直到Power函式執行完成,迴圈退出。

yield return除了可以返回IEnumerable之外,還能直接返回IEnumerator,不過直接返回IEnumerator不能使用foreach進行迭代,上面的例子可以改寫為

public class PowersOf2
{
    static void Main()
    {
        // Display powers of 2 up to the exponent of 8:
        var enumerator = Power(2,8);
        while( enumerator.MoveNext() )
        {
            Console.WriteLine("{0} ",enumerator.Current);
        }
    }
    public static IEnumerator<int> Power(int number, int exponent)
    {
       ……
    }
    // Output: 2 4 8 16 32 64 128 256
}

coroutines

實際上問題到此已經很清楚了,我沒有看過coroutines的原始碼,但通過它對yied和IEnumerator的依賴,可以猜測協程實際上就是yield迭代的過程。

當我們使用StartCoroutine時,Unity引擎會通過某種方式進行迭代,從而達到類似於多執行緒的效果。

參考連結: