C# —— IEnumerable和狀態機
在上一篇文章中,我們看了一下列舉器以及.NET如何使用foreach迴圈,我們看到了列舉器實際上是如何通過使用MoveNext方法和Current屬性從一個狀態轉換到另一個狀態的物件。
我們知道,如果我們想要建立一個自定義列舉器,我們將需要實現IEnumerator介面或它的泛型 副本,這是狀態發揮作用和狀態機的地方。檢視列舉器的Current屬性是如果成為物件還是泛型型別,我們可以利用這個優勢,並實現從計算、到列舉,甚至整個工作流程的各種演算法。所有“魔法”實際上都發生在MoveNext方法中,我們可以在其中執行任何從一個狀態轉換到另一個狀態所需的操作。
所有這些對於理解我們是否希望通過實現
但在大多數情況下,.NET提供的泛型集合綽綽有餘,除非我們想要建立一個非常專業的迭代器或結構,如圖形和二叉樹。
這就把我們帶到了關於.NET編譯器在幕後做什麼以使我們的生活更輕鬆的主題,我知道我們已經走了很長一段路,但是要更好地理解它是如何組合在一起的(所做的是值得的)。
現在輸入我們的客人,yield關鍵字。為此,我準備了一個示例,以便更好地視覺化
public IEnumerable<int> Fibs (int fibCount)
{
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
我們這裡有一個返回所有“ fibCount” 斐波那契數列的方法,請注意yield關鍵字。當
你可以在你的方法中擁有任意數量的yield語句,不需要將它放在迴圈中,並且它將始終從它執行的最後一個返回行繼續,讓我們看一個例子:
public IEnumerable<int> GetSomeIntegers()
{
yield return 1;
yield return 2;
yield return 3;
}
此方法將返回1,然後在下一次呼叫時它將返回2,然後在下一次呼叫後它將返回3。
但是yield構造具有另一種形式,也就是yield break,它會告訴列舉器它已經到達其範圍的末尾,以下是示範:
IEnumerable<string> Foo (bool breakEarly)
{
yield return "One";
yield return "Two";
if (breakEarly)
yield break;
yield return "Three";
}
此示例僅返回“One”和“Two”,如果breakEarly引數為true,則永遠不會達到“Three” 。
所以你看,使用yield return和yield break,我們可以設計一個複雜的工作流程,而無需實現我們自己的任何列舉器,並輕鬆使用外部引數。
接下來,我將向您展示一個違反正常執行流程的示例,並展示了LINQ如何通過其擴充套件方法在幕後工作,以及如何編寫列舉器。
static void Main()
{
foreach (int fib in EvenNumbersOnly(Fibs(6)))
{
Console.WriteLine (fib);
}
}
static IEnumerable<int> Fibs (int fibCount)
{
for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
static IEnumerable<int> EvenNumbersOnly (IEnumerable<int> sequence)
{
foreach (int x in sequence)
if ((x % 2) == 0)
yield return x;
}
在這裡,我們有一個列舉器組合的例子。乍一看,我們希望Fibs首先執行,但這是違反工作流邏輯的部分,程式將首先進入EvenNumberOnly方法,然後當它到達foreach內部時,它才會實際進入Fibs方法。然後它實際上將繼續執行foreach直到它可以返回一個值,此時它會將它寫入螢幕,然後該過程從它停止的地方再次開始,保持兩個EvenNumberOnly和Fibs列舉器的狀態直到Fibs完成,此時EvenNumberOnly也將完成。
這就是LINQ如何允許我們連結多個操作並“實時”處理大型資料集,而不是在每個步驟中遍歷整個元素集合。使用這種技術,我們還可以處理來自Web服務的分頁資料,而無需預先進行大量呼叫並將其儲存在記憶體中。
儘管它是一個非常好用且有用的功能,但我們必須記住,該yield 構造有一些限制:
- 該yield關鍵字只能用於返回IEnumerable形式的方法。
- 該yield關鍵字不能被用在try- catch塊(其理由是,當一個異常被丟擲,則列舉變得無效並且它將被釋放),但它可以被用在try- finally塊。
- 該方法不能包含ref或out引數。
- 它不能用於unsafe塊。
- 它不能用在anonymous方法中,如lambda表示式。
總之,我們看到了如何實現自定義列舉器,而不必經歷製作我們自己的自定義型別的麻煩,以及我們如何利用它foreach來做更多的事情而不僅僅是迭代一組專案。
原文地址:https://www.codeproject.com/Articles/1266944/IEnumerable-and-State-Machines