本文只是 C# 5.0 規范中的內容,稍作調整,主要是下載 Demo 看看,這玩意用法挺多。
使用迭代器塊實現的函數成員稱為迭代器(iterator)。(所謂函數成員,包括方法、屬性、事件、索引器、用戶定義運算符、實例構造函數、靜態構造函數和析構函數。)
只要相應函數成員的返回類型是枚舉器接口 enumerator 或可枚舉接口 enumerable 之一,迭代器塊就可用作該函數成員的函數體。如下“音樂標題”類所示,有三個函數,有返回 IEnumerator 的,還有返回 IEnumerable 的,其中,返回 Reverse 函數可以反序迭代一個集合,而 Subset 函數可以迭代一個集合的子集:
public class MusicTitles
{
string[] names = { "Tubular Bells", "Hergest Ridge", "Ommadawn", "Platinum" };
public IEnumerator GetEnumerator()
{
for (int i = 0; i < 4; i++)
{
yield return names[i];
}
}
public IEnumerable Reverse()
{
for (int i = 3; i >= 0; i--)
{
yield return names[i];
}
}
public IEnumerable Subset(int index, int length)
{
for (int i = index; i < index + length; i++)
{
yield return names[i];
}
}
}
可以用如下方式迭代:
foreach (string title in titles)
{
Console.WriteLine(title);
}
foreach (string title in titles.Reverse())
{
Console.WriteLine(title);
}
foreach (string title in titles.Subset(2, 2))
{
Console.WriteLine(title);
}
迭代器塊可以是 method-body、Operator-body 或 accessor-body,而不能將事件、實例構造函數、靜態構造函數和析構函數作為迭代器來實現。
當使用迭代器塊實現函數成員時,為該函數成員的形參列表指定任何 ref 或 out 形參將產生編譯時錯誤。
枚舉器接口 (enumerator interface) 為非泛型接口 System.Collections.IEnumerator 和泛型接口 System.Collections.Generic.IEnumerator<T> 的所有實例化。
簡潔起見,將這些接口分別表示為 IEnumerator 和 IEnumerator<T>。
可枚舉接口 (enumerable interface) 為非泛型接口 System.Collections.IEnumerable 和泛型接口 System.Collections.Generic.IEnumerable<T> 的所有實例化。
簡潔起見,將這些接口分別表示為 IEnumerable 和 IEnumerable<T>。
迭代器產生一系列值,所有值的類型均相同。此類型稱為迭代器的產生類型 (yield type)。
C# 1.0 使用 foreach 語句可以迭代集合,但創建枚舉器需要大量代碼。C# 2.0 添加了 yield 語句,便于創建枚舉器。
如果返回枚舉器接口類型的函數成員是使用迭代器塊實現的,調用該函數成員不會立即執行迭代器塊中的代碼。而是先創建并返回一個枚舉器對象 (enumerator object)。此對象封裝了在迭代器塊中指定的代碼,并且在調用該枚舉器對象的 MoveNext 方法時執行該迭代器塊中的代碼。枚舉器對象具有下列特點:
枚舉器對象通常是編譯器生成的枚舉器類的一個實例,它封裝了迭代器塊中的代碼,并實現了枚舉器接口,但也可能實現其他方法。如果枚舉器類由編譯器生成,則該類將直接或間接嵌套在包含該函數成員的類中,它將具有私有可訪問性,并且它將具有一個供編譯器使用的保留名稱。
枚舉器對象可實現除上面指定的那些接口以外的其他接口。
下面的各節將描述由枚舉器對象所提供的 IEnumerable 和 IEnumerable<T> 接口實現的 MoveNext、Current 和 Dispose 成員的確切行為。
請注意,枚舉器對象不支持 IEnumerator.Reset 方法。調用此方法將導致引發 System.NotSupportedException。
枚舉器對象的 MoveNext 方法封裝了迭代器塊的代碼。調用 MoveNext 方法將執行迭代器塊中的代碼,并相應設置枚舉器對象的 Current 屬性。MoveNext 執行的具體操作取決于調用 MoveNext 時的枚舉器對象的狀態:
當 MoveNext 執行迭代器塊時,可以采用四種方式來中斷執行:通過 yield return 語句、通過 yield break 語句、到達迭代器塊的末尾以及引發異常并將異常傳播到迭代器塊之外。
枚舉器對象的 Current 屬性將受迭代器塊中的 yield return 語句影響。
當枚舉器對象處于掛起 (suspended) 狀態時,Current 的值為上一次調用 MoveNext 時設置的值。當枚舉器對象處于運行前 (before)、運行中 (running) 或運行后 (after) 狀態時,訪問 Current 的結果不確定。
對于產生類型不是 object 的迭代器,通過枚舉器對象的 IEnumerable 實現來訪問 Current 的結果對應于通過枚舉器對象的 IEnumerator<T> 實現來訪問 Current 并將該結果強制轉換為 object。
Dispose 方法用于通過使枚舉器對象變為運行后 (after) 狀態來清除迭代。
如果返回可枚舉接口類型的函數成員是使用迭代器塊實現的,調用該函數成員不會立即執行迭代器塊中的代碼。而是先創建并返回一個可枚舉對象 (enumerable object)。可枚舉對象的 GetEnumerator 方法返回一個封裝有迭代器塊中指定的代碼的枚舉器對象,當調用該枚舉器對象的 MoveNext 方法時,將執行迭代器塊中的代碼??擅杜e對象具有下列特點:
可枚舉對象通常是編譯器生成的可枚舉類的實例,它封裝了迭代器塊中的代碼,并實現了可枚舉接口,但也可能實現其他方法。如果可枚舉類由編譯器生成,則該類將直接或間接嵌套在包含該函數成員的類中,它將具有私有可訪問性,并且它將具有供編譯器使用的保留名稱。
可枚舉對象可實現除上面指定的那些接口以外的其他接口。具體而言,可枚舉對象還可實現 IEnumerator 和 IEnumerator<T>,從而使其既可作為可枚舉對象,也可作為枚舉器對象。在該類型的實現中,首次調用可枚舉對象的 GetEnumerator 方法時,將返回可枚舉對象本身。對可枚舉對象的 GetEnumerator 的后續調用(如果存在),將返回可枚舉對象的副本。因此,每個返回的枚舉器都有自己的狀態,一個枚舉器中的更改不會影響其他枚舉器。
可枚舉對象實現了 IEnumerable 和 IEnumerable<T> 接口的 GetEnumerator 方法。這兩種 GetEnumerator 方法的實現是相同的,都是獲取并返回一個可用的枚舉器對象。枚舉器對象是以初始化該可枚舉對象時保存的實例值和實參值進行初始化的,此外,枚舉器對象函數如前所述。
下面的 Stack<T> 類使用一個迭代器實現其 GetEnumerator 方法。
規范中的該示例有編譯錯誤,所以采用 MSDN 中的實例:
public class Stack<T> : IEnumerable<T>
{
PRivate T[] items;
private int count;
public void Push(T item)
{
if (items == null)
{
items = new T[4];
}
else if (items.Length == count)
{
T[] newItems = new T[count * 2];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
items[count++] = item;
}
public T Pop()
{
T result = items[--count];
items[count] = default(T);
return result;
}
public IEnumerator<T> GetEnumerator()
{
for (int i = count - 1; i >= 0; --i)
yield return items[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerable<T> TopToBottom
{
get { return this; }
}
public IEnumerable<T> BottomToTop
{
get
{
for (int index = 0; index <= count - 1; index++)
{
yield return items[index];
}
}
}
public IEnumerable<T> TopN(int itemsFromTop)
{
// Return less than itemsFromTop if necessary.
int startIndex = itemsFromTop >= count ? 0 : count - itemsFromTop;
for (int index = count - 1; index >= startIndex; index--)
{
yield return items[index];
}
}
}
若用如下代碼:
Stack<int> stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
stack.Push(3);
stack.Push(4);
stack.Push(5);
foreach (int s in stack)
{
Console.Write(s + " ");
}
stack.Pop();
Console.Write("/n");
foreach (int s in stack)
{
Console.Write(s + " ");
}
Console.Write("/n");
foreach (int s in stack.TopToBottom)
{
Console.Write(s + " ");
}
Console.Write("/n");
foreach (int s in stack.BottomToTop)
{
Console.Write(s + " ");
}
Console.Write("/n");
foreach (int s in stack.TopN(2))
{
Console.Write(s + " ");
}
Console.ReadKey();
運行結果:
5 4 3 2 1
4 3 2 1
4 3 2 1
1 2 3 4
4 3
下載 Demo
新聞熱點
疑難解答