迭代器 (C#)Iterators (C#)

「迭代器」** 可用來逐步執行集合,例如清單和陣列。An iterator can be used to step through collections such as lists and arrays.

迭代器方法或 get 存取子會對集合執行自訂反覆運算。An iterator method or get accessor performs a custom iteration over a collection. 迭代器方法使用 yield return 陳述式,一次傳回一個項目。An iterator method uses the yield return statement to return each element one at a time. 當到達 yield return 陳述式時,系統會記住程式碼中的目前位置。When a yield return statement is reached, the current location in code is remembered. 下次呼叫迭代器函式時,便會從這個位置重新開始執行。Execution is restarted from that location the next time the iterator function is called.

您會使用 foreach 陳述式或使用 LINQ 查詢,透過用戶端程式碼取用迭代器。You consume an iterator from client code by using a foreach statement or by using a LINQ query.

在下列範例中,第一次反覆運算 foreach 迴圈會使 SomeNumbers 迭代器方法中的執行繼續,直到到達第一個 yield return 陳述式為止。In the following example, the first iteration of the foreach loop causes execution to proceed in the SomeNumbers iterator method until the first yield return statement is reached. 此反覆運算會傳回值 3,並保留迭代器方法中的目前位置。This iteration returns a value of 3, and the current location in the iterator method is retained. 下次反覆運算迴圈時,迭代器方法中的執行會從上次停止的位置繼續,並且在到達 yield return 陳述式時再次停止。On the next iteration of the loop, execution in the iterator method continues from where it left off, again stopping when it reaches a yield return statement. 此反覆運算會傳回值 5,並再次保留迭代器方法中的目前位置。This iteration returns a value of 5, and the current location in the iterator method is again retained. 當到達迭代器方法結尾時,迴圈便完成。The loop completes when the end of the iterator method is reached.

static void Main()
{
    foreach (int number in SomeNumbers())
    {
        Console.Write(number.ToString() + " ");
    }
    // Output: 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>The return type of an iterator method or get accessor can be IEnumerable, IEnumerable<T>, IEnumerator, or IEnumerator<T>.

您可以使用 yield break 陳述式結束反覆項目。You can use a yield break statement to end the iteration.

注意

如需本主題中除簡易迭代器範例以外的其他所有範例,請加入 System.CollectionsSystem.Collections.Generic 命名空間的 using 指示詞。For all examples in this topic except the Simple Iterator example, include using directives for the System.Collections and System.Collections.Generic namespaces.

簡單的 IteratorSimple Iterator

下列範例在 for 迴圈內有一行 yield return 陳述式。The following example has a single yield return statement that is inside a for loop. Main 中,每次反覆運算 foreach 陳述式主體都會建立迭代器函式的呼叫,以繼續進行下一個 yield return 陳述式。In Main, each iteration of the foreach statement body creates a call to the iterator function, which proceeds to the next yield return statement.

static void Main()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    // Output: 6 8 10 12 14 16 18
    Console.ReadKey();
}

public static System.Collections.Generic.IEnumerable<int>
    EvenSequence(int firstNumber, int lastNumber)
{
    // Yield even numbers in the range.
    for (int number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

建立集合類別Creating a Collection Class

在以下範例中,DaysOfTheWeek 類別會實作 IEnumerable 介面,而這個介面需使用 GetEnumerator 方法。In the following example, the DaysOfTheWeek class implements the IEnumerable interface, which requires a GetEnumerator method. 編譯器會隱含呼叫 GetEnumerator 方法,以傳回 IEnumeratorThe compiler implicitly calls the GetEnumerator method, which returns an IEnumerator.

GetEnumerator 方法使用 yield return 陳述式,一次傳回一個字串。The GetEnumerator method returns each string one at a time by using the yield return statement.

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];
        }
    }
}

下列範例會建立 Zoo 類別,其中包含動物的集合。The following example creates a Zoo class that contains a collection of animals.

參考類別執行個體 (theZoo) 的 foreach 陳述式會隱含呼叫 GetEnumerator 方法。The foreach statement that refers to the class instance (theZoo) implicitly calls the GetEnumerator method. 參考 BirdsMammals 屬性的 foreach 陳述式會使用 AnimalsForType 具名迭代器方法。The foreach statements that refer to the Birds and Mammals properties use the AnimalsForType named iterator method.

static void Main()
{
    Zoo theZoo = new Zoo();

    theZoo.AddMammal("Whale");
    theZoo.AddMammal("Rhinoceros");
    theZoo.AddBird("Penguin");
    theZoo.AddBird("Warbler");

    foreach (string name in theZoo)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Whale Rhinoceros Penguin Warbler

    foreach (string name in theZoo.Birds)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Penguin Warbler

    foreach (string name in theZoo.Mammals)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Whale Rhinoceros

    Console.ReadKey();
}

public class Zoo : IEnumerable
{
    // Private members.
    private List<Animal> animals = new List<Animal>();

    // Public methods.
    public void AddMammal(string name)
    {
        animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
    }

    public void AddBird(string name)
    {
        animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird });
    }

    public IEnumerator GetEnumerator()
    {
        foreach (Animal theAnimal in animals)
        {
            yield return theAnimal.Name;
        }
    }

    // Public members.
    public IEnumerable Mammals
    {
        get { return AnimalsForType(Animal.TypeEnum.Mammal); }
    }

    public IEnumerable Birds
    {
        get { return AnimalsForType(Animal.TypeEnum.Bird); }
    }

    // Private methods.
    private IEnumerable AnimalsForType(Animal.TypeEnum type)
    {
        foreach (Animal theAnimal in animals)
        {
            if (theAnimal.Type == type)
            {
                yield return theAnimal.Name;
            }
        }
    }

    // Private class.
    private class Animal
    {
        public enum TypeEnum { Bird, Mammal }

        public string Name { get; set; }
        public TypeEnum Type { get; set; }
    }
}

搭配泛型清單使用迭代器Using Iterators with a Generic List

在以下範例中,Stack<T> 泛型類別會實作 IEnumerable<T> 泛型介面。In the following example, the Stack<T> generic class implements the IEnumerable<T> generic interface. Push 方法會將值指派給 T 類型的陣列。The Push method assigns values to an array of type T. GetEnumerator 方法會使用 yield return 陳述式以傳回陣列值。The GetEnumerator method returns the array values by using the yield return statement.

除了泛型 GetEnumerator 方法,您也必須實作非泛型 GetEnumerator 方法。In addition to the generic GetEnumerator method, the non-generic GetEnumerator method must also be implemented. 這是因為 IEnumerable<T> 繼承自 IEnumerableThis is because IEnumerable<T> inherits from IEnumerable. 非泛型實作會延後到泛型實作。The non-generic implementation defers to the generic implementation.

此範例使用具名迭代器來支援逐一查看相同資料集合的各種方法。The example uses named iterators to support various ways of iterating through the same collection of data. 這些具名迭代器是 TopToBottomBottomToTop 屬性,以及 TopN 方法。These named iterators are the TopToBottom and BottomToTop properties, and the TopN method.

BottomToTop 屬性會在 get 存取子中使用迭代器。The BottomToTop property uses an iterator in a get accessor.

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];
        }
    }

}

語法資訊Syntax Information

出現的迭代器可以是方法或 get 存取子。An iterator can occur as a method or get accessor. 迭代器不能出現在事件、執行個體建構函式、靜態建構函式或靜態完成項中。An iterator cannot occur in an event, instance constructor, static constructor, or static finalizer.

從語句中的運算式類型 yield return 到反覆運算器所傳回之的型別引數,都必須有隱含轉換 IEnumerable<T>An implicit conversion must exist from the expression type in the yield return statement to the type argument for the IEnumerable<T> returned by the iterator.

在 C# 中,迭代器方法不可有任何 inrefout 參數。In C#, an iterator method cannot have any in, ref, or out parameters.

在 c # 中,不是 yield 保留字,只有在或關鍵字之前使用時才具有特殊意義 return breakIn C#, yield is not a reserved word and has special meaning only when it is used before a return or break keyword.

技術實作Technical Implementation

雖然您將迭代器撰寫成方法,但編譯器會將其轉譯成巢狀類別,其實也就是狀態機器。Although you write an iterator as a method, the compiler translates it into a nested class that is, in effect, a state machine. 此類別會在用戶端程式碼中的 foreach 迴圈繼續期間追蹤迭代器的位置。This class keeps track of the position of the iterator as long the foreach loop in the client code continues.

若要查看編譯器的功能,您可以使用 Ildasm.exe 工具,檢視為迭代器方法產生的 Microsoft 中繼語言程式碼。To see what the compiler does, you can use the Ildasm.exe tool to view the Microsoft intermediate language code that's generated for an iterator method.

當您建立 classstruct 的迭代器時,您不需要實作整個 IEnumerator 介面。When you create an iterator for a class or struct, you don't have to implement the whole IEnumerator interface. 當編譯器偵測到迭代器時,它會自動產生 IEnumeratorIEnumerator<T> 介面的 CurrentMoveNextDispose 方法。When the compiler detects the iterator, it automatically generates the Current, MoveNext, and Dispose methods of the IEnumerator or IEnumerator<T> interface.

之後每次反覆運算 foreach 迴圈 (或直接呼叫 IEnumerator.MoveNext),下一個迭代器程式碼主體都會在上一個 yield return 陳述式之後繼續。On each successive iteration of the foreach loop (or the direct call to IEnumerator.MoveNext), the next iterator code body resumes after the previous yield return statement. 然後繼續執行至下一個 yield return 陳述式,直到達到迭代器主體結尾,或遇到 yield break 陳述式為止。It then continues to the next yield return statement until the end of the iterator body is reached, or until a yield break statement is encountered.

迭代器不支援 IEnumerator.Reset 方法。Iterators don't support the IEnumerator.Reset method. 若要從頭開始逐一查看,您必須取得新的迭代器。To reiterate from the start, you must obtain a new iterator. 對迭代器方法傳回的迭代器呼叫 Reset 會擲回 NotSupportedExceptionCalling Reset on the iterator returned by an iterator method throws a NotSupportedException.

如需其他資訊,請參閱 C# 語言規格For additional information, see the C# Language Specification.

迭代器的使用Use of Iterators

當您需要使用複雜的程式碼來填入清單序列時,迭代器可讓您維持 foreach 迴圈的簡潔性。Iterators enable you to maintain the simplicity of a foreach loop when you need to use complex code to populate a list sequence. 當您想要執行下列作業時,這會很有用:This can be useful when you want to do the following:

  • 在第一次反覆運算 foreach 迴圈之後修改清單序列。Modify the list sequence after the first foreach loop iteration.

  • 避免在第一次反覆運算 foreach 迴圈之前完整載入大型清單。Avoid fully loading a large list before the first iteration of a foreach loop. 分頁擷取以分批載入資料表資料列即為一例。An example is a paged fetch to load a batch of table rows. 另一個範例是 EnumerateFiles 方法,它會在 .net 中執行反覆運算器。Another example is the EnumerateFiles method, which implements iterators in .NET.

  • 在迭代器中封裝建立清單。Encapsulate building the list in the iterator. 在迭代器方法中,您可以建立清單,然後在迴圈中產生每個結果。In the iterator method, you can build the list and then yield each result in a loop.

請參閱See also