[譯]C# 中的列舉程式

來源:互聯網
上載者:User
出處:http://www.ondotnet.com/pub/a/dotnet/2004/06/07/liberty.html
PDF 瀏覽:http://www.tracefact.net/Document/Iterators-In-CSharp.pdf

C# 中的列舉程式術語表

Iterator:列舉程式

如果你正在建立一個表現和行為都類似於集合的類,允許類的使用者使用foreach語句對集合中的成員進行枚舉將會是很方便的。這在C# 2.0中比 C# 1.1更容易實現一些。作為示範,我們先在 C# 1.1中為一個簡單的集合添加枚舉,然後我們修改這個範例,使用新的C#2.0 枚舉構建方法。

我們將以建立一個簡單化的List Box作為開始,它將包含一個8字串的數組和一個整型,這個整型用於記錄數組中已經添加了多少字串。建構函式將對數組進行初始化並使用傳遞進來的參數填充它。

public ListBox(params string[] initialStrings)
{
    strings = new String[8];

    foreach (string s in initialStrings)
    {
       strings[ctr++] = s;
    }
}

除此以外,ListBox類還需要一個Add方法(進行添加 string 的操作) 和 一個返回數組中字串個數的方法。

public void Add(string theString)
{
    strings[ctr] = theString;
    ctr++;
}

public int GetNumEntries()
{
    return ctr;
}

 

NOTE:實際開發中,通常使用ArrayList,而不是固定大小的數組。在這裡為了程式簡單就沒有做數組下標越界的檢測。

從感覺上看,ListBox像是一個集合,如果可以使用集合中通常使用的 foreach 迴圈來擷取listBox中的所有字串將會是非常便利的。如此的話,可以這樣書寫代碼:

ListBox lb = new ListBox("a", "b", "c", "d", "e", "f", "g", "h");
foreach (string s in lb) {
    Console.WriteLine(s);
}

但是,會得到這樣一個錯誤:

“Iterator.ListBox”不包含“GetEnumerator”的公用定義,因此 foreach 語句不能作用於“Iterator.ListBox”類型的變數

想要使用foreach語句,還必須實現IEnumerable 介面。

這個介面只要求實現一個方法: GetEnumerator。這個方法必須返回一個實現了IEnumerator 介面的對象。除此以外,我們需要返回的這個對象不僅實現了IEnumerator,而且知道如何枚舉ListBox對象。你將需要建立一個 ListBoxEmunerator(在下面描述):

NOTE: IEnumerable 和 IEnumerator 是不同的介面,請不要搞混了。

 

public IEnumerator GetEnumerator()
{
    return new ListBoxEnumerator();
}

現在,ListBox 可以使用 foreach 迴圈了:

ListBox lbt = new ListBox("Hello", "World");

lbt.Add("Who");
lbt.Add("Is");
lbt.Add("John");
lbt.Add("Galt");

foreach (string s in lbt)
{
    Console.WriteLine("Value: {0}", s);
}

先是執行個體化這個ListBox ,並初始了兩個字串,隨後又添加了四個。foreach迴圈接受ListBox執行個體,並且迭代它,依次返回字串。輸出是:

Hello
World
Who
Is
John
Galt

實現 IEnumerator 介面

注意到ListBoxEnumerator不僅需要實現IEnumerator介面,對於ListBox類它也需要一些特別瞭解;特別是,它必須可以獲得ListBox的字串數組並且遍曆其所包含的字串。IEnumerable 類和與其相關的 IEnumerator類之間的關係有一點微妙。實現IEnumerator介面的最好辦法是在IEnumerable類裡建立一個嵌套的IEnumerator類。

public class ListBox : IEnumerable
{
    // 嵌套的私人ListBoxEnumerator類實現
    private class ListBoxEnumerator : IEnumerator
    {
       // 代碼實現...
    }
    // ListBox類的代碼...
}

注意ListBoxEnumerator需要對它所嵌入的ListBox類的一個引用。你可以通過ListBoxEnumerator的建構函式來傳遞。

為了實現IEnumerator介面,ListBoxEnumerator需要兩個方法:MoveNext和Reset,還有一個屬性:Current。這些方法和屬性的任務是建立一個狀態機器制,確保你可以在任何時候得知ListBox中的哪個元素是當前元素,並獲得那個元素。

在這個例子中,這種狀態機器制是通過維護一個標明當前string的索引值來完成的,並且,你可以通過對外部類的string集合進行索引來返回這個當前的string。為了達到這個目標,你需要一個成員變數儲存對於外部ListBox對象的引用,以及一個整型用於儲存當前索引。

private ListBox lbt;
private int index;

每次Reset方法被調用的時候,index被置為 -1。

public void Reset()
{
    index = -1;
}

每次MoveNext被調用的時候,外部類的數組檢查時候已經到了末尾,如果是這樣,方法返回false。如果集合中還有對象,index將增加,並且方法返回true。

public bool MoveNext()
{
    index++;
    if (index >= lbt.strings.Length)
    {
       return false;
    }else
    {
       return true;
    }
}

最後,如果MoveNext方法返回True,foreach迴圈將調用Current屬性。ListBoxEnumerator的Current屬性的實現是索引外部類(ListBox)中的集合,並且返回找到的對象(這個例子中,是一個字串)。注意,返回一個Object是因為IEnumerator介面中Current屬性的簽名如此。

public object Current
{
    get {
       return(lbt[index]);
    }
}

在1.1中,所有想要通過foreach迴圈來迭代的類都需要實現IEnumerable介面,於是,必須建立一個實現了IEnumerator的類。最糟的是,enumerator返回的值並不是型別安全的。記得Current屬性返回一個Object對象;它僅僅簡單的假設你所返回的值與foreach迴圈所期望的相符合。

C# 2.0 的解救辦法

使用C# 2.0 這些問題如同五月末的雪般融化了。在這個例子的2.0版本中,我重寫上面的列表,使用C# 2.0的兩個新特性:泛型 和 列舉程式。

我以重新定義實現IEumerable<string>的ListBox作為開始:

public class ListBox : IEnumerable<string>

這樣做確定這個類可以在foreach迴圈中使用,同時確保迭代的值是string類型。

現在,從上個例子中挪去整個嵌套類,並且用下面的代碼替換 GetEnumerator方法。

public IEnumerator<string> GetEnumerator()
{
   foreach (string s in strings)
   {
      yield return s;
   }
}

GetEnumerator方法使用了新的 yield 語句。yield語句返回一個運算式。yield語句僅在迭代塊中出現,並且返回foreach語句所期望的值。那也就是,對GetEnumerator的每次調用都將會產生集合中的下一個字串;所有的狀態管理已經都為你做好了!

就這樣了,你已經完成了。不需要為每個類型實現你自己的enumerator,不需要建立嵌套類。你已經移除了至少30行代碼,並且極大地簡化了你的代碼。程式繼續像期望的那樣運行,但是狀態管理不再是你的任務,所有的都為你做好了。更進一步,由列舉程式所返回的值一定是string類型,如果你想要返回其他類型,你可以修改IEnumerable泛型語句,IEnumerable泛型語句將反射新類型。

關於Yield的更多內容

作為對上一節的一些說明,應該告訴你:實際上,你可以在yield語句塊中yield一個以上的值。這樣,下面的語句是完全正確的C#語句:

public IEnumerator GetEnumerator()
{
   yield return "Who";
   yield return " is";
   yield return "John Galt?";
}

假設上面的代碼位於一個名為foo的類中,你可以這樣寫:

foreach ( string s in new foo())
{
   Console.Write(s);
}

輸出結果將會是:

Who is John Galt?

如果你現在停下來思考一下,這些也是之前的代碼所做的事。它遍曆了自己的foreach迴圈,並且產生出它所找到的每個string字串。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.