C#2.0新增功能05 迭代器

  迭代器可用於逐步迭代集合,例如列表和數組。html

  迭代器方法或 get 訪問器可對集合執行自定義迭代。 迭代器方法使用 yield return 語句返回元素,每次返回一個。 到達 yield return 語句時,會記住當前在代碼中的位置。 下次調用迭代器函數時,將從該位置從新開始執行。api

經過 foreach 語句或 LINQ 查詢從客戶端代碼中使用迭代器。數組

  在如下示例中,foreach 循環的首次迭代致使 SomeNumbers 迭代器方法繼續執行,直至到達第一個 yield return 語句。 此迭代返回的值爲 3,並保留當前在迭代器方法中的位置。 在循環的下次迭代中,迭代器方法的執行將從其暫停的位置繼續,直至到達 yield return 語句後纔會中止。 此迭代返回的值爲 5,並再次保留當前在迭代器方法中的位置。 到達迭代器方法的結尾時,循環便已完成。ide

static void Main()
{
    foreach (int number in SomeNumbers())
    {
        Console.Write(number.ToString() + " ");
    }
    // 輸出: 3 5 8
    Console.ReadKey();
}

public static System.Collections.IEnumerable SomeNumbers()
{
    yield return 3;
    yield return 5;
    yield return 8;
}

迭代器方法或 get 訪問器的返回類型能夠是 IEnumerableIEnumerable<T>IEnumeratorIEnumerator<T>函數

可使用 yield break 語句來終止迭代。工具

對於本主題中除簡單迭代器示例之外的全部示例,請爲 System.Collections 和 System.Collections.Generic 命名空間加入 using 指令。ui

簡單的迭代器
 下例包含一個位於 for 循環內的 yield return 語句。 Main 中,foreach 語句體的每次迭代都會建立一個對迭代器函數的調用,並將繼續到下一個 yield return 語句。
static void Main()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    // 輸出: 6 8 10 12 14 16 18
    Console.ReadKey();
}

public static System.Collections.Generic.IEnumerable<int> EvenSequence(int firstNumber, int lastNumber)
{
    // 迭代集合中的偶數.
    for (int number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}
建立集合類

在如下示例中,DaysOfTheWeek 類實現 IEnumerable 接口,此操做須要 GetEnumerator 方法。 編譯器隱式調用 GetEnumerator 方法,此方法返回 IEnumeratorthis

GetEnumerator 方法經過使用 yield return 語句每次返回 1 個字符串。spa

static void Main()
{
    DaysOfTheWeek days = new DaysOfTheWeek();

    foreach (string day in days)
    {
        Console.Write(day + " ");
    }
    // 輸出: Sun Mon Tue Wed Thu Fri Sat
    Console.ReadKey();
}

public class DaysOfTheWeek : IEnumerable
{
    private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

    public IEnumerator GetEnumerator()
    {
        for (int index = 0; index < days.Length; index++)
        {
            // 迭代每一天
            yield return days[index];
        }
    }
}

下例建立了一個包含動物集合的 Zoo 類。code

引用類實例 (theZoo) 的 foreach 語句隱式調用 GetEnumerator 方法。 引用 BirdsMammals 屬性的 foreach 語句使用 AnimalsForType 命名迭代器方法。

 1 static void Main()
 2 {
 3     Zoo theZoo = new Zoo();
 4 
 5     theZoo.AddMammal("Whale");
 6     theZoo.AddMammal("Rhinoceros");
 7     theZoo.AddBird("Penguin");
 8     theZoo.AddBird("Warbler");
 9 
10     foreach (string name in theZoo)
11     {
12         Console.Write(name + " ");
13     }
14     Console.WriteLine();
15     // 輸出: Whale Rhinoceros Penguin Warbler
16 
17     foreach (string name in theZoo.Birds)
18     {
19         Console.Write(name + " ");
20     }
21     Console.WriteLine();
22     // 輸出: Penguin Warbler
23 
24     foreach (string name in theZoo.Mammals)
25     {
26         Console.Write(name + " ");
27     }
28     Console.WriteLine();
29     // 輸出: Whale Rhinoceros
30 
31     Console.ReadKey();
32 }
33 
34 public class Zoo : IEnumerable
35 {
36     // 私有成員
37     private List<Animal> animals = new List<Animal>();
38 
39     // 公共方法
40     public void AddMammal(string name)
41     {
42         animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
43     }
44 
45     public void AddBird(string name)
46     {
47         animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird });
48     }
49 
50     public IEnumerator GetEnumerator()
51     {
52         foreach (Animal theAnimal in animals)
53         {
54             yield return theAnimal.Name;
55         }
56     }
57 
58     // 公共成員
59     public IEnumerable Mammals
60     {
61         get { return AnimalsForType(Animal.TypeEnum.Mammal); }
62     }
63 
64     public IEnumerable Birds
65     {
66         get { return AnimalsForType(Animal.TypeEnum.Bird); }
67     }
68 
69     // 私有方法
70     private IEnumerable AnimalsForType(Animal.TypeEnum type)
71     {
72         foreach (Animal theAnimal in animals)
73         {
74             if (theAnimal.Type == type)
75             {
76                 yield return theAnimal.Name;
77             }
78         }
79     }
80 
81     // 私有類
82     private class Animal
83     {
84         public enum TypeEnum { Bird, Mammal }
85 
86         public string Name { get; set; }
87         public TypeEnum Type { get; set; }
88     }
89 }
對泛型列表使用迭代器

在如下示例中,Stack<T> 泛型類實現 IEnumerable<T> 泛型接口。 Push 方法將值分配給類型爲 T 的數組。 GetEnumerator 方法經過使用 yield return 語句返回數組值。

除了泛型 GetEnumerator 方法,還必須實現非泛型 GetEnumerator 方法。 這是由於從 IEnumerable 繼承了 IEnumerable<T>非泛型實現聽從泛型實現的規則。

本示例使用命名迭代器來支持經過各類方法循環訪問同一數據集合。 這些命名迭代器爲 TopToBottomBottomToTop 屬性,以及 TopN 方法。

BottomToTop 屬性在 get 訪問器中使用迭代器。

  1 static void Main()
  2 {
  3     Stack<int> theStack = new Stack<int>();
  4 
  5     //  向堆棧中添加項
  6     for (int number = 0; number <= 9; number++)
  7     {
  8         theStack.Push(number);
  9     }
 10 
 11     // 從堆棧中檢索項。
 12     // 此處容許使用 foreach,由於 foreach 實現了 IEnumerable<int>
 13     foreach (int number in theStack)
 14     {
 15         Console.Write("{0} ", number);
 16     }
 17     Console.WriteLine();
 18     // 輸出: 9 8 7 6 5 4 3 2 1 0
 19 
 20     // 此處容許使用 foreach,由於 theStack.TopToBottom 屬性返回了 IEnumerable(Of Integer).
 21     foreach (int number in theStack.TopToBottom)
 22     {
 23         Console.Write("{0} ", number);
 24     }
 25     Console.WriteLine();
 26     // 輸出: 9 8 7 6 5 4 3 2 1 0
 27 
 28     foreach (int number in theStack.BottomToTop)
 29     {
 30         Console.Write("{0} ", number);
 31     }
 32     Console.WriteLine();
 33     // 輸出: 0 1 2 3 4 5 6 7 8 9
 34 
 35     foreach (int number in theStack.TopN(7))
 36     {
 37         Console.Write("{0} ", number);
 38     }
 39     Console.WriteLine();
 40     // 輸出: 9 8 7 6 5 4 3
 41 
 42     Console.ReadKey();
 43 }
 44 
 45 public class Stack<T> : IEnumerable<T>
 46 {
 47     private T[] values = new T[100];
 48     private int top = 0;
 49 
 50     public void Push(T t)
 51     {
 52         values[top] = t;
 53         top++;
 54     }
 55     public T Pop()
 56     {
 57         top--;
 58         return values[top];
 59     }
 60 
 61     // 此方法實現了GetEnumerator()方法. 它容許在 foreach 語句中使用類的實例。
 63     public IEnumerator<T> GetEnumerator()
 64     {
 65         for (int index = top - 1; index >= 0; index--)
 66         {
 67             yield return values[index];
 68         }
 69     }
 70 
 71     IEnumerator IEnumerable.GetEnumerator()
 72     {
 73         return GetEnumerator();
 74     }
 75 
 76     public IEnumerable<T> TopToBottom
 77     {
 78         get { return this; }
 79     }
 80 
 81     public IEnumerable<T> BottomToTop
 82     {
 83         get
 84         {
 85             for (int index = 0; index <= top - 1; index++)
 86             {
 87                 yield return values[index];
 88             }
 89         }
 90     }
 91 
 92     public IEnumerable<T> TopN(int itemsFromTop)
 93     {
 94         // 若有必要,返回少於 itemsFromTop 
 95         int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;
 96 
 97         for (int index = top - 1; index >= startIndex; index--)
 98         {
 99             yield return values[index];
100         }
101     }
102 
103 }
語法信息

迭代器可用做一種方法,或一個 get 訪問器。 不能在事件、實例構造函數、靜態構造函數或靜態終結器中使用迭代器。

必須存在從 yield return 語句中的表達式類型到迭代器返回的 IEnumerable<T> 類型參數的隱式轉換。

在 C# 中,迭代器方法不能有任何 inrefout 參數。

在 C# 中,「yield」不是保留字,只有在 returnbreak 關鍵字以前使用時纔有特殊含義。

技術實現

即便將迭代器編寫成方法,編譯器也會將其轉換爲其實是狀態機的嵌套類。 只要客戶端代碼中的 foreach 循環繼續,此類就會跟蹤迭代器的位置。

若要查看編譯器執行的操做,可以使用 Ildasm.exe 工具查看爲迭代器方法生成的 Microsoft 中間語言代碼。

結構建立迭代器時,沒必要實現整個 IEnumerator 接口。 編譯器檢測到迭代器時,會自動生成 IEnumeratorIEnumerator<T> 接口的 CurrentMoveNextDispose 方法。

foreach 循環(或對 IEnumerator.MoveNext 的直接調用)的每次後續迭代中,下一個迭代器代碼體都會在上一個 yield return 語句以後恢復。 而後繼續下一個 yield return 語句,直至到達迭代器體的結尾,或直至遇到 yield break 語句。

迭代器不支持 IEnumerator.Reset 方法。 若要從頭開始從新迭代,必須獲取新的迭代器。 在迭代器方法返回的迭代器上調用 Reset 會引起 NotSupportedException

有關其餘信息,請參閱 C# 語言規範

迭代器的使用

須要使用複雜代碼填充列表序列時,使用迭代器可保持 foreach 循環的簡單性。 需執行如下操做時,這可能頗有用:

  • 在第一次 foreach 循環迭代以後,修改列表序列。

  • 避免在 foreach 循環的第一次迭代以前徹底加載大型列表。 一個示例是用於加載一批表格行的分頁提取。 另外一個示例關於 EnumerateFiles 方法,該方法在 .NET Framework 中實現迭代器。

  • 在迭代器中封裝生成列表。 使用迭代器方法,可生成該列表,而後在循環中產出每一個結果。

 

相關文章
相關標籤/搜索