反覆運算器(Visual Basic)Iterators (Visual Basic)

「迭代器」可用來逐步執行集合,例如清單和陣列。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. Iterator 方法會使用Yield語句,一次傳回一個元素。An iterator method uses the Yield statement to return each element one at a time. 當到達 Yield 陳述式時,系統會記住程式碼中的目前位置。When a Yield statement is reached, the current location in code is remembered. 下次呼叫迭代器函式時,便會從這個位置重新開始執行。Execution is restarted from that location the next time the iterator function is called.

您會從用戶端程式代碼取用反覆運算器,方法是使用For Each 。下一個語句,或使用 LINQ 查詢。You consume an iterator from client code by using a For Each…Next statement, or by using a LINQ query.

在下列範例中,第一次反覆運算 For Each 迴圈會使 SomeNumbers 迭代器方法中的執行繼續,直到到達第一個 Yield 陳述式為止。In the following example, the first iteration of the For Each loop causes execution to proceed in the SomeNumbers iterator method until the first Yield statement is reached. 此反覆運算會傳回值 3,並保留迭代器方法中的目前位置。This iteration returns a value of 3, and the current location in the iterator method is retained. 下次反覆運算迴圈時,迭代器方法中的執行會從上次停止的位置繼續,並且在到達 Yield 陳述式時再次停止。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 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.

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  

迭代器方法或 get 存取子的傳回型別可以是 IEnumerableIEnumerable<T>IEnumeratorIEnumerator<T>The return type of an iterator method or get accessor can be IEnumerable, IEnumerable<T>, IEnumerator, or IEnumerator<T>.

您可以使用Exit FunctionReturn語句來結束反復專案。You can use an Exit Function or Return statement to end the iteration.

Visual Basic 反覆運算器函數或get存取子宣告包含iterator修飾詞。A Visual Basic iterator function or get accessor declaration includes an Iterator modifier.

反覆運算器是在 Visual Studio 2012 的 Visual Basic 中引進。Iterators were introduced in Visual Basic in Visual Studio 2012.

本主題內容In this topic

注意

針對主題中的所有範例(簡單反覆運算器範例除外),包含System.CollectionsSystem.Collections.Generic命名空間的 Imports 語句。For all examples in the topic except the Simple Iterator example, include Imports statements for the System.Collections and System.Collections.Generic namespaces.

簡易迭代器Simple Iterator

下列範例的單一Yield語句位於For 。下一個迴圈。The following example has a single Yield statement that is inside a For…Next loop. Main 中,每次反覆運算 For Each 陳述式主體都會建立迭代器函式的呼叫,以繼續進行下一個 Yield 陳述式。In Main, each iteration of the For Each statement body creates a call to the iterator function, which proceeds to the next Yield statement.

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  

建立集合類別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.

方法會使用語句,一次傳回一個字串,而Iterator修飾詞則是在函式宣告中。 Yield GetEnumeratorThe GetEnumerator method returns each string one at a time by using the Yield statement, and an Iterator modifier is in the function declaration.

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  

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

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

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  

Try 區塊Try Blocks

Visual Basic 允許在Yield Try Try 的區塊中使用語句 ... Catch 。Finally 語句Visual Basic allows a Yield statement in the Try block of a Try...Catch...Finally Statement. 具有語句的Catch區塊可以有區塊,而且可以有Finally區塊。 Try YieldA Try block that has a Yield statement can have Catch blocks, and can have a Finally block.

下列範例會在Tryiterator Catch函式Finally中包含、和區塊。The following example includes Try, Catch, and Finally blocks in an iterator function. Iterator Finally函數中的區塊會在For Each反復專案完成之前執行。The Finally block in the iterator function executes before the For Each iteration finishes.

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  

語句不能在Catch區塊或Finally區塊內。 YieldA Yield statement cannot be inside a Catch block or a Finally block.

如果主體(而不是 iterator 方法)擲回例外狀況Catch ,則Finally不會執行 iterator 函式中的區塊,但會執行 iterator 函數中的區塊。 For EachIf the For Each body (instead of the iterator method) throws an exception, a Catch block in the iterator function is not executed, but a Finally block in the iterator function is executed. Iterator Catch函式內的區塊只會攔截反覆運算器函數內所發生的例外狀況。A Catch block inside an iterator function catches only exceptions that occur inside the iterator function.

匿名方法Anonymous Methods

在 Visual Basic 中,匿名函式可以是反覆運算器函數。In Visual Basic, an anonymous function can be an iterator function. 下列範例將說明這點。The following example illustrates this.

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()  

下列範例具有可驗證引數的非反覆運算器方法。The following example has a non-iterator method that validates the arguments. 方法會傳回匿名反覆運算器的結果,以描述集合元素。The method returns the result of an anonymous iterator that describes the collection elements.

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主體的第一個反復專案開始為止。If validation is instead inside the iterator function, the validation cannot be performed until the start of the first iteration of the For Each body.

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

在以下範例中,Stack(Of T) 泛型類別會實作 IEnumerable<T> 泛型介面。In the following example, the Stack(Of T) generic class implements the IEnumerable<T> generic interface. Push 方法會將值指派給 T 類型的陣列。The Push method assigns values to an array of type T. GetEnumerator 方法會使用 Yield 陳述式以傳回陣列值。The GetEnumerator method returns the array values by using the Yield 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屬性宣告Iterator包含關鍵字。The BottomToTop property declaration includes the Iterator keyword.

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  

語法資訊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 destructor.

Yield 陳述式中的運算式類型必須隱含轉換成迭代器的傳回型別。An implicit conversion must exist from the expression type in the Yield statement to the return type of the iterator.

在 Visual Basic 中,iterator 方法不能有ByRef任何參數。In Visual Basic, an iterator method cannot have any ByRef parameters.

在 Visual Basic 中,"Yield" 不是保留字,只有在Iterator方法或get存取子中使用時才具有特殊意義。In Visual Basic, "Yield" is not a reserved word and has special meaning only when it is used in an Iterator method or get accessor.

技術實作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. 此類別會在用戶端程式碼中的 For Each...Next 迴圈繼續期間追蹤迭代器的位置。This class keeps track of the position of the iterator as long the For Each...Next 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 is generated for an iterator method.

當您建立類別結構的反覆運算器時,您不需要執行整個IEnumerator介面。When you create an iterator for a class or struct, you do not 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.

之後每次反覆運算 For Each…Next 迴圈 (或直接呼叫 IEnumerator.MoveNext),下一個迭代器程式碼主體都會在上一個 Yield 陳述式之後繼續。On each successive iteration of the For Each…Next loop (or the direct call to IEnumerator.MoveNext), the next iterator code body resumes after the previous Yield statement. 接著,它會繼續進行Yield下一個語句,直到到達反覆運算器主體的結尾,或遇到Exit FunctionReturn語句為止。It then continues to the next Yield statement until the end of the iterator body is reached, or until an Exit Function or Return statement is encountered.

反覆運算器不支援IEnumerator.Reset方法。Iterators do not support the IEnumerator.Reset method. 若要從頭開始逐一查看,您必須取得新的迭代器。To re-iterate from the start, you must obtain a new iterator.

如需詳細資訊,請參閱Visual Basic 語言規格For additional information, see the Visual Basic Language Specification.

迭代器的使用Use of Iterators

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

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

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

  • 在迭代器中封裝建立清單。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