1. 程式人生 > >C# —— IEnumerable和狀態機

C# —— IEnumerable和狀態機

在上一篇文章,我們看了一下列舉器以及.NET如何使用foreach迴圈,我們看到了列舉器實際上是如何通過使用MoveNext方法和Current屬性從一個狀態轉換到另一個狀態的物件。

我們知道,如果我們想要建立一個自定義列舉器,我們將需要實現IEnumerator介面或它的泛型 副本,這是狀態發揮作用和狀態機的地方。檢視列舉器的Current屬性是如果成為物件還是泛型型別,我們可以利用這個優勢,並實現從計算、到列舉,甚至整個工作流程的各種演算法。所有魔法實際上都發生在MoveNext方法中,我們可以在其中執行任何從一個狀態轉換到另一個狀態所需的操作。

所有這些對於理解我們是否希望通過實現

IEnumerable介面或者泛型IEnumerable版本來實現我們自己的集合是必不可少的,因為我們必須告訴我們的集合應該如何遍歷它,這意味著返回一個列舉器並實現MoveNext方法,基本上我們需要實現一個列舉器,或者如果我們在自己的實現中有一個內部集合,那麼只需傳遞它。

但在大多數情況下,.NET提供的泛型集合綽綽有餘,除非我們想要建立一個非常專業的迭代器或結構,如圖形和二叉樹。

這就把我們帶到了關於.NET編譯器在幕後做什麼以使我們的生活更輕鬆的主題,我知道我們已經走了很長一段路,但是要更好地理解它是如何組合在一起的(所做的是值得的)。

現在輸入我們的客人,yield關鍵字。為此,我準備了一個示例,以便更好地視覺化

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關鍵字。當

.NET編譯器遇到yield關鍵字時,它會檢視方法返回型別(在這種情況下,它是型別  intIEnumerable),並在後臺生成一個列舉器,因此這實際上會建立一個列舉整數的物件,因此  yield return組合是相當於列舉器的Current屬性和方法的其餘部分,直到滿足另一個 yield ,這相當於  MoveNext方法。通過另一個yield,我的意思是,就像手動實現的列舉器一樣,它將保留其當前狀態並在下次遇到 yield時使用它 。所以在這種情況下,第一次時函式將返回1,第二次呼叫返回1,第三次呼叫返回2,依此類推,直到  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 returnyield 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直到它可以返回一個值,此時它會將它寫入螢幕,然後該過程從它停止的地方再次開始,保持兩個EvenNumberOnlyFibs列舉器的狀態直到Fibs完成,此時EvenNumberOnly也將完成。

這就是LINQ如何允許我們連結多個操作並實時處理大型資料集,而不是在每個步驟中遍歷整個元素集合。使用這種技術,我們還可以處理來自Web服務的分頁資料,而無需預先進行大量呼叫並將其儲存在記憶體中。

儘管它是一個非常好用且有用的功能,但我們必須記住,該yield 構造有一些限制:

  • yield關鍵字只能用於返回IEnumerable形式的方法。
  • yield關鍵字不能被用在trycatch塊(其理由是,當一個異常被丟擲,則列舉變得無效並且它將被釋放),但它可以被用在tryfinally塊。
  • 該方法不能包含refout引數。
  • 它不能用於unsafe塊。
  • 它不能用在anonymous方法中,如lambda表示式。

總之,我們看到了如何實現自定義列舉器,而不必經歷製作我們自己的自定義型別的麻煩,以及我們如何利用它foreach來做更多的事情而不僅僅是迭代一組專案。

 

原文地址:https://www.codeproject.com/Articles/1266944/IEnumerable-and-State-Machines