Iterator (C# 和 Visual Basic)

Iterator 可以用來逐步執行集合 (例如逐步執行清單和陣列)。

Iterator 方法或 get 存取子執行對集合的自訂反覆項目。 Iterator 方法使用 yieldVisual Basicyield return 或 (C#) 陳述式傳回每個項目一次。 當 Yield 或 yield return 陳述式到達時,目前在程式碼的位置會被記住。 下一次呼叫此 Iterator 時,便會從這個位置重新開始執行。

您可以使用用戶端程式碼的 Iterator 使用 For Each…Next (Visual Basic) 或 foreach (C#) 使用 LINQ 查詢,陳述式或。

在下列範例中, For Each 或 foreach 迴圈的第一個反覆項目在直到第一個 Yield 的 SomeNumbers Iterator 方法會執行執行或 yield return 陳述式為止。 這個反覆項目傳回值為 3,因此, Iterator 方法的目前位置保留。 在迴圈的下一個反覆項目,在 Iterator 方法的執行與繼續它停止,再停止的位置,並在到達 Yield 或 yield return 陳述式。 這個反覆項目傳回值為 5,因此, Iterator 方法的目前位置再保留。 當 Iterator 方法結尾到達,迴圈完成。

Sub Main()
    For Each number As Integer In SomeNumbers()
        Console.Write(number & " ")
    Next
    ' Output: 3 5 8
    Console.ReadKey()
End Sub

Private Iterator Function SomeNumbers() As System.Collections.IEnumerable
    Yield 3
    Yield 5
    Yield 8
End Function
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;
}

Iterator 方法或 get 存取子的傳回型別可以是 IEnumerableIEnumerable<T>IEnumeratorIEnumerator<T>

您可以使用 Exit Function 或 Return 陳述式 (Visual Basic) 或 yield break 陳述式 (C#) 結束反覆運算。

Visual Basic Iterator 函式或 get 存取子宣告包含Iterator 修飾詞。

Iterator 被引進C#中的Visual Studio 2005中,且被引進到Visual Studio 2012的 Visual Basic 中。

本主題內容

  • 簡單的 Iterator

  • 建立一個集合類別

  • 在 Visual Basic 中的 Try 區塊

  • 在 Visual Basic 中的匿名方法

  • 使用泛型清單的 Iterator

  • 語法資訊

  • 技術實作

  • 使用 Iterator

簡單的 Iterator

下列範例會在 For…Next 的單一 Yield 或 yield return 陳述式 (Visual Basic) 或 for (C#) 迴圈內。 在Main中,每個 For Each或foreach的iteration的陳述式主體建立一個呼叫函式 Iterator的呼叫,繼續執行下個Yield或yield return 陳述式。

Sub Main()
    For Each number As Integer In EvenSequence(5, 18)
        Console.Write(number & " ")
    Next
    ' Output: 6 8 10 12 14 16 18
    Console.ReadKey()
End Sub

Private Iterator Function EvenSequence(
ByVal firstNumber As Integer, ByVal lastNumber As Integer) _
As System.Collections.Generic.IEnumerable(Of Integer)

    ' Yield even numbers in the range.
    For number As Integer = firstNumber To lastNumber
        If number Mod 2 = 0 Then
            Yield number
        End If
    Next
End Function
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;
        }
    }
}

建立一個集合類別

注意事項注意事項

如需本主題的其餘的範例,請將 匯入 陳述式 (Visual Basic) 或 使用 指示詞 (C#) System.CollectionsSystem.Collections.Generic 命名空間。

在下列範例中, DaysOfTheWeek 類別實作 IEnumerable 介面,需要 GetEnumerator 方法。 編譯器會隱含地呼叫 GetEnumerator 方法,傳回 IEnumerator

藉由使用 Yield 或 yield return 陳述式, GetEnumerator 方法一次傳回一個字串。 在 Visual Basic 程式碼, Iterator 修飾詞在函式宣告裡。

Sub Main()
    Dim days As New DaysOfTheWeek()
    For Each day As String In days
        Console.Write(day & " ")
    Next
    ' Output: Sun Mon Tue Wed Thu Fri Sat
    Console.ReadKey()
End Sub

Private Class DaysOfTheWeek
    Implements IEnumerable

    Public days =
        New String() {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}

    Public Iterator Function GetEnumerator() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        ' Yield each day of the week.
        For i As Integer = 0 To days.Length - 1
            Yield days(i)
        Next
    End Function
End Class
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 類別。

For Each 或 foreach 陳述式指稱到類別執行個體 (theZoo)隱含對 GetEnumerator 的呼叫。 參考 Birds 和 Mammals 屬性的 For Each 或 foreach 陳述式使用具名的 Iterator 方法 AnimalsForType 。

Sub Main()
    Dim theZoo As New Zoo()

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

    For Each name As String In theZoo
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Whale Rhinoceros Penguin Warbler

    For Each name As String In theZoo.Birds
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Penguin Warbler

    For Each name As String In theZoo.Mammals
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Whale Rhinoceros

    Console.ReadKey()
End Sub

Public Class Zoo
    Implements IEnumerable

    ' Private members.
    Private animals As New List(Of Animal)

    ' Public methods.
    Public Sub AddMammal(ByVal name As String)
        animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Mammal})
    End Sub

    Public Sub AddBird(ByVal name As String)
        animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Bird})
    End Sub

    Public Iterator Function GetEnumerator() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        For Each theAnimal As Animal In animals
            Yield theAnimal.Name
        Next
    End Function

    ' Public members.
    Public ReadOnly Property Mammals As IEnumerable
        Get
            Return AnimalsForType(Animal.TypeEnum.Mammal)
        End Get
    End Property

    Public ReadOnly Property Birds As IEnumerable
        Get
            Return AnimalsForType(Animal.TypeEnum.Bird)
        End Get
    End Property

    ' Private methods.
    Private Iterator Function AnimalsForType( _
    ByVal type As Animal.TypeEnum) As IEnumerable
        For Each theAnimal As Animal In animals
            If (theAnimal.Type = type) Then
                Yield theAnimal.Name
            End If
        Next
    End Function

    ' Private class.
    Private Class Animal
        Public Enum TypeEnum
            Bird
            Mammal
        End Enum

        Public Property Name As String
        Public Property Type As TypeEnum
    End Class
End Class
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; }
    }
}

在 Visual Basic 中的 Try 區塊

Visual Basic 允許在 Try...Catch...Finally 陳述式 (Visual Basic)的 Try 區塊中的 Yield 陳述式。 具有 Yield 陳述式的 Try 區塊有 Catch 區塊,而且可以有 Finally 區塊。

C# 注意事項C# 注意事項

C# 允許在 try-finally 陳述式的 try 區塊中的 yield return 陳述式。具有 yield return 陳述式的 try 區塊不可以有任何 catch 區塊。

下列 Visual Basic 範例包括 Try, Catch,和Finally 區塊在 Iterator 的函式中運作。 在 For Each 反覆項目結束前,在函式的 Finally Iterator 區塊執行。

Sub Main()
    For Each number As Integer In Test()
        Console.WriteLine(number)
    Next
    Console.WriteLine("For Each is done.")

    ' Output:
    '  3
    '  4
    '  Something happened. Yields are done.
    '  Finally is called.
    '  For Each is done.
    Console.ReadKey()
End Sub

Private Iterator Function Test() As IEnumerable(Of Integer)
    Try
        Yield 3
        Yield 4
        Throw New Exception("Something happened. Yields are done.")
        Yield 5
        Yield 6
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    Finally
        Console.WriteLine("Finally is called.")
    End Try
End Function

Yield 陳述式不可以在 Catch 區塊或 Finally 區塊內。

如果 For Each 主體 (而不是 Iterator 方法) 會擲回例外狀況,在函式的 Catch Iterator 區塊,並不會執行,但在 Iterator 函式的 Finally 區塊執行。 在 Iterator 的函式中 Catch 區塊攔截發生在 Iterator 函式內的例外狀況。

在 Visual Basic 中的匿名方法

在 Visual Basic (但不在 C# 中),匿名函式可以是 Iterator 函式。 下列範例將說明這點。

Dim iterateSequence = Iterator Function() _
                      As IEnumerable(Of Integer)
                          Yield 1
                          Yield 2
                      End Function

For Each number As Integer In iterateSequence()
    Console.Write(number & " ")
Next
' Output: 1 2
Console.ReadKey()

下列範例會 Visual Basic 驗證引數的非 Iterator 方法。 方法會傳回描述集合項目匿名 Iterator 的結果。

Sub Main()
    For Each number As Integer In GetSequence(5, 10)
        Console.Write(number & " ")
    Next
    ' Output: 5 6 7 8 9 10
    Console.ReadKey()
End Sub

Public Function GetSequence(ByVal low As Integer, ByVal high As Integer) _
As IEnumerable
    ' Validate the arguments.
    If low < 1 Then
        Throw New ArgumentException("low is too low")
    End If
    If high > 140 Then
        Throw New ArgumentException("high is too high")
    End If

    ' Return an anonymous iterator function.
    Dim iterateSequence = Iterator Function() As IEnumerable
                              For index = low To high
                                  Yield index
                              Next
                          End Function
    Return iterateSequence()
End Function

如果驗證是 Iterator 函式內,驗證無法執行直到 For Each 主體的第一個反覆項目的開始。

使用泛型清單的 Iterator

在下列範例中,Stack(Of T)泛型類別會實作 IEnumerable<T> 泛型介面。 Push 方法指派值給陣列型別 T。 您可以使用 Yield 或 yield return 陳述式, GetEnumerator 方法傳回陣列值。

除了泛型 GetEnumerator 方法之外,也必須實作非泛型 GetEnumerator 方法。 這是因為, IEnumerable<T>IEnumerable繼承。 非泛型實作延後至泛型實作。

這個範例使用具名的 Iterator 將相同的資料集合支援各種方式逐一查看。 這些具名 Iterator 為 TopToBottom 和 BottomToTop 屬性和 TopN 方法。

BottomToTop 屬性在 get 存取子中使用 Iterator。 在Visual Basic程式碼中, 屬性宣告中包含 Iterator 關鍵字。

Sub Main()
    Dim theStack As New Stack(Of Integer)

    ' Add items to the stack.
    For number As Integer = 0 To 9
        theStack.Push(number)
    Next

    ' Retrieve items from the stack.
    ' For Each is allowed because theStack implements
    ' IEnumerable(Of Integer).
    For Each number As Integer In theStack
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3 2 1 0

    ' For Each is allowed, because theStack.TopToBottom
    ' returns IEnumerable(Of Integer).
    For Each number As Integer In theStack.TopToBottom
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3 2 1 0

    For Each number As Integer In theStack.BottomToTop
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 0 1 2 3 4 5 6 7 8 9 

    For Each number As Integer In theStack.TopN(7)
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3

    Console.ReadKey()
End Sub

Public Class Stack(Of T)
    Implements IEnumerable(Of T)

    Private values As T() = New T(99) {}
    Private top As Integer = 0

    Public Sub Push(ByVal t As T)
        values(top) = t
        top = top + 1
    End Sub

    Public Function Pop() As T
        top = top - 1
        Return values(top)
    End Function

    ' This function implements the GetEnumerator method. It allows
    ' an instance of the class to be used in a For Each statement.
    Public Iterator Function GetEnumerator() As IEnumerator(Of T) _
        Implements IEnumerable(Of T).GetEnumerator

        For index As Integer = top - 1 To 0 Step -1
            Yield values(index)
        Next
    End Function

    Public Iterator Function GetEnumerator1() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        Yield GetEnumerator()
    End Function

    Public ReadOnly Property TopToBottom() As IEnumerable(Of T)
        Get
            Return Me
        End Get
    End Property

    Public ReadOnly Iterator Property BottomToTop As IEnumerable(Of T)
        Get
            For index As Integer = 0 To top - 1
                Yield values(index)
            Next
        End Get
    End Property

    Public Iterator Function TopN(ByVal itemsFromTop As Integer) _
        As IEnumerable(Of T)

        ' Return less than itemsFromTop if necessary.
        Dim startIndex As Integer =
            If(itemsFromTop >= top, 0, top - itemsFromTop)

        For index As Integer = top - 1 To startIndex Step -1
            Yield values(index)
        Next
    End Function
End Class
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];
        }
    }

}

語法資訊

Iterator 可以發生在方法或 get 存取子。 Iterator 在事件、執行個體建構函式、靜態建構函式或靜態解構函式不會發生。

隱含轉換必須存在於運算式的現有型別 YieldVisual Basicyield return 或 (C#) 陳述式的 Iterator 的傳回型別。

在 Visual Basic中,Iterator 方法不能有任何ByRef 參數。 在C#,Iterator 方法不能有任何 ref或out 參數。

在 Visual Basic, 「yield」不是保留字而且具有特殊意義,只有在用在 Iterator 方法或 get 存取子時。 在 C# 中, 「yield」不是保留字而且具有特殊意義,只有當他在 return 或 break 關鍵字前面時使用。

技術實作

雖然您可以將 Iterator 撰寫為方法,編譯器 (Compiler) 卻會將其轉譯為巢狀類別,也就是實際上的狀態機器。 只要用戶端程式碼上的For Each...Next或foreach 迴圈持續進行,這個類別就會持續追蹤 Iterator 的位置。

若要查看編譯器在幕後進行的動作,請使用 Ildasm.exe 工具來檢視為 Iterator 方法產生的中繼語言 (IL) 程式碼。

當你建立為類別結構 (Struct) 建立 Iterator 時,您並不需要實作整個 IEnumerator 介面。 編譯器會在偵測到您的 Iterator 時,自動產生 IEnumeratorIEnumerator<T> 介面的 Current、MoveNext 和 Dispose 方法。

在 For Each…Next 或 foreach 迴圈 (或對 IEnumerator.MoveNext的直接呼叫) 的每個後續反覆項目,在前一個 Yield 或 yield return 陳述式之後下一個 Iterator 程式碼主體繼續執行。 接著它會繼續進行到下個 Yield 或 yield return 陳述式,直到 Iterator 主體結尾為止,或直到 Exit Function 或 Return 陳述式 (Visual Basic) 或 yield break 陳述式 (C#) 遇到。

這些 Iterator 都不支援 IEnumerator.Reset 方法。 若要從頭開始重新執行反覆查看,您必須取得新的 Iterator。

如需其他資訊,請參閱 Visual Basic 語言規格C# 語言規格

使用 Iterator

當您需要使用複雜的程式碼以填入清單 Iterator 序列時,Iterators讓您維護 For Each 迴圈的簡單性。 這個方法在您實作下列項目時有用:

  • 修改清單序列,在第一 For Each 迴圈反覆項目之後。

  • 避免完全載入大型的清單,在 For Each 迴圈的第一個反覆項目之前。 範例會載入批次的已呼叫擷取資料表資料列。 另一個範例是 EnumerateFiles 方法,實作於 .NET Framework 中的 Iterator。

  • 封裝建立清單在 Iterator中。 在 Iterator 方法中,您可以建立清單在迴圈中可產生每個結果。

下列 C# 網誌提供使用 Iterator 的其他資訊。

請參閱

參考

For Each...Next 陳述式 (Visual Basic)

foreach、in (C# 參考)

Yield 陳述式 (Visual Basic)

yield (C# 參考)

Iterator (Visual Basic)

搭配陣列使用 foreach (C# 程式設計手冊)

泛型 (C# 程式設計手冊)

System.Collections.Generic

IEnumerable<T>