迭代器用於遍歷集合。迭代器可定義為方法或get訪問器。在event, 實例構造函數,靜態構造函數以及靜態析構函數中不能使用迭代器。
yield 關鍵字專門為迭代器而設計。通過 yield定義迭代器,在實現IEnumerable 和 IEnumerator 接口以自定義集合時無需添加其他顯式類(保存枚舉狀態)。
yield 語句有兩種形式:
yield return <expression>; yield break;
yield return 語句一次返回一個元素:foreach 語句或LINQ查詢每次迭代都會調用對應迭代方法,該迭代方法運行到 yield return 語句時,會返回一個expression,並保留當前的運行位置,下次調用迭代器函數時直接從該位置開始。
yield break 語句用於終止迭代。
迭代器的聲明必須滿足以下條件:
返回IEnumerable或IEnumerator的迭代器,其yield類型為object。如果迭代器返回的類型為IEnumerable<T>或IEnumerator<T>,則必須把yield return語句的表達式類型隱式轉換為泛型類型參數的類型。
具有以下特點的方法不能包含yield return或yield break語句:
不能將yield return語句放在try-catch塊中,但可以放在try-finally語句的try塊中。
yield break語句可放在try塊或catch塊中,但不能放在finally塊中。
如果foreach語句(迭代器之外)發生異常,將執行迭代器的finally塊。
雖然我們以方法的形式定義迭代器,但是編譯器會將其轉換為嵌套類。該類會對迭代器的位置進行了記錄。
在為類創建迭代器時,不用完全實現IEnumerator接口。當編譯器檢測到迭代器時,會自動為生成IEnumerator或IEnumerator<T>接口的Current, MoveNext以及Dispose方法。
迭代器不支持IEnumerator.Reset方法,要重新遍歷,必須獲取一個新的迭代器。
下面代碼先從一個迭代器返回IEnumerable<string>,然後遍歷其元素:
IEnumerable<string> elements = MyIteratorMethod();
foreach (string element in elements)
{
…
}
調用MyIteratorMethod時不執行實際操作,在foreach循環時,為elements調用MoveNext方法,才真正執行遍歷操作,直至下一個yield return 語句。
在foreach循環的每個後續迭代中,迭代器主體的執行將從它暫停的位置繼續,直至到達yield return語句後才會停止。在到達迭代器方法的結尾或yield break語句時,foreach循環完成。
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.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
}
上例中,for循環包含一個yield return語句。Main中的foreach循環每次迭代都會調用Power迭代器函數。對迭代器函數的每次調用都會從上次結束的地方開始。
public static class GalaxyClass
{
public static void ShowGalaxies()
{
var theGalaxies = new Galaxies();
foreach (Galaxy theGalaxy in theGalaxies.NextGalaxy)
{
Debug.WriteLine(theGalaxy.Name + " " + theGalaxy.MegaLightYears.ToString());
}
}
public class Galaxies
{
public System.Collections.Generic.IEnumerable<Galaxy> NextGalaxy
{
get
{
yield return new Galaxy { Name = "Tadpole", MegaLightYears = 400 };
yield return new Galaxy { Name = "Pinwheel", MegaLightYears = 25 };
yield return new Galaxy { Name = "Milky Way", MegaLightYears = 0 };
yield return new Galaxy { Name = "Andromeda", MegaLightYears = 3 };
}
}
}
public class Galaxy
{
public String Name { get; set; }
public int MegaLightYears { get; set; }
}
}
上例對get訪問器形式的迭代器進行了演示,在該示例中,每個yield return語句返回一個用戶自定義類的實例。
在例中,DaysOfTheWeek 類實現了IEnumerable接口,即提供GetEnumerator方法。在迭代DaysOfTheWeek集合類時,編譯器會隱式調用GetEnumerator方法,得到IEnumerator。GetEnumerator方法通過yield return語句每次返回一個字符串。
static void Main()
{
DaysOfTheWeek days = new DaysOfTheWeek();
foreach (string day in days)
{
Console.Write(day + " ");
}
// Output: 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 each day of the week.
yield return days[index];
}
}
}
static void Main()
{
Stack<int> theStack = new Stack<int>();
// Add items to the stack.
for (int number = 0; number <= 9; number++)
{
theStack.Push(number);
}
// Retrieve items from the stack.
// foreach is allowed because theStack implements
// IEnumerable<int>.
foreach (int number in theStack)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
// foreach is allowed, because theStack.TopToBottom
// returns IEnumerable(Of Integer).
foreach (int number in theStack.TopToBottom)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3 2 1 0
foreach (int number in theStack.BottomToTop)
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 0 1 2 3 4 5 6 7 8 9
foreach (int number in theStack.TopN(7))
{
Console.Write("{0} ", number);
}
Console.WriteLine();
// Output: 9 8 7 6 5 4 3
Console.ReadKey();
}
public class Stack<T> : IEnumerable<T>
{
private T[] values = new T[100];
private int top = 0;
public void Push(T t)
{
values[top] = t;
top++;
}
public T Pop()
{
top--;
return values[top];
}
// This method implements the GetEnumerator method. It allows
// an instance of the class to be used in a foreach statement.
public IEnumerator<T> GetEnumerator()
{
for (int index = top - 1; index >= 0; index--)
{
yield return values[index];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerable<T> TopToBottom
{
get { return this; }
}
public IEnumerable<T> BottomToTop
{
get
{
for (int index = 0; index <= top - 1; index++)
{
yield return values[index];
}
}
}
public IEnumerable<T> TopN(int itemsFromTop)
{
// Return less than itemsFromTop if necessary.
int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;
for (int index = top - 1; index >= startIndex; index--)
{
yield return values[index];
}
}
}
在上面的例子中,Stack<T>泛型類實現了IEnumerable<T>泛型接口。Push方法將T類型值添加到數組,GetEnumerator方法通過yield return語句包含數組值。
除了泛型的GetEnumerator方法,還必須實現非泛型的GetEnumerator方法。因為IEnumerable<T>從IEnumerable繼承而來。非泛型直接通過泛型實現。
該示例使用命名迭代器以支持對同一集合的多種迭代方式。命名迭代器包括TopToBottom,BottomToTop以及TopN方法。
其中,BottomToTop屬性在get訪問器中使用了迭代器。