迭代器可用來遍歷清單和陣列等集合。
迭代器方法或 get 存取子會在集合上執行自訂的迭代。 反覆運算器方法會使用 Yield 語句一次傳回每個元素。 當到達Yield語句時,會記住程式碼中的目前位置。 下次呼叫反覆運算器函式時,會從該位置重新啟動執行。
您可以使用 For Each…Next 語句或 LINQ 查詢從客戶端程式碼取用迭代器。
在下列範例中,For Each 迴圈的第一次迭代會導致執行 SomeNumbers 迭代器方法,直到到達第一個 Yield 語句為止。 這個迭代會傳回值 3,並保留在迭代方法中的目前位置。 在下一次迴圈執行中,迭代器方法中的執行將從上次停止的位置繼續,當它到達 Yield 語句時會停止。 這次迭代會傳回值 5,而迭代器方法中的目前位置仍然保留。 當到達反覆運算器方法的結尾時,迴圈就會完成。
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 存取子的傳回型別可以是 IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T>。
您可以使用 Exit Function 或 Return 語句結束反覆專案。
Visual Basic 迭代函式或 get 存取子宣告包含 Iterator 修飾詞。
反覆運算器是在 Visual Studio 2012 的 Visual Basic 中引進的。
簡單反覆運算器
下列範例包含位於 Yield 迴圈中的單一 語句。 在 Main 中,For Each 語句主體的每個迴圈都會建立對迭代器函式的呼叫,該函式會繼續到下一個 Yield 語句。
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
建立集合類別
在下列範例中,類別DaysOfTheWeek實作了需要IEnumerable方法的GetEnumerator介面。 編譯程式會隱含呼叫 GetEnumerator 方法,這個方法會 IEnumerator傳回 。
方法 GetEnumerator 會使用 Yield 語句一次傳回每個字串,而且 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
下列範例會建立包含 Zoo 動物集合的類別。
For Each參考類別實例 (theZoo) 的語句會隱含呼叫 GetEnumerator 方法。
For Each語句參考Birds和Mammals屬性,使用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
嘗試區塊
Visual Basic 允許 Yield 語句在 Try 的 區塊中。 具有Try語句的Yield區塊可以有Catch區塊,而且可以有Finally區塊。
下列範例會在反覆運算器函式中包含 Try、Catch 和 Finally 區塊。
Finally 迭代器函式中的區塊會在迴圈迭代完成之前For Each執行。
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 主體(而不是反覆運算方法)拋出例外狀況,則反覆運算功能中的Catch 區塊不會執行,但反覆運算功能中的Finally 區塊會執行。 迭代器函式中的 Catch 區塊僅攔截迭代器函式中發生的例外狀況。
匿名方法
在 Visual Basic 中,匿名函式可以是反覆運算器函式。 下列範例說明這點。
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()
下列範例具有驗證自變數的非反覆運算器方法。 方法會傳回描述集合元素之匿名迭代器的結果。
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
如果驗證是放在反覆運算器函式內,那麼直到For Each主體第一次反覆運算開始時才可以執行驗證。
使用迭代器搭配泛型清單
在下列範例中 Stack(Of T) ,泛型類別會實作 IEnumerable<T> 泛型介面。 方法 Push 會將值指派給類型的 T陣列。 方法 GetEnumerator 會使用 Yield 語句傳回陣列值。
除了泛型 GetEnumerator 方法之外,也必須實作非泛型 GetEnumerator 方法。 這是因為 IEnumerable<T> 繼承自 IEnumerable。 非泛型實作會延遲至泛型實作。
此範例使用命名迭代器來支援各種遍歷相同集合數據的方法。 這些具名反覆運算器是 TopToBottom 和 BottomToTop 屬性,以及 TopN 方法。
屬性 BottomToTop 宣告包含 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
語法資訊
反覆運算器可以作為方法或 get 存取子。 反覆運算器無法在事件、實例建構函式、靜態建構函式或靜態解構函式中發生。
隱含轉換必須存在於Yield語句中的表達式類型與迭代器的回傳類型之間。
在 Visual Basic 中,反覆運算器方法不能有任何 ByRef 參數。
在 Visual Basic 中,“Yield” 不是保留字,只有在 Iterator 方法或存取子 get 中使用時,才會有特殊意義。
技術實作
雖然您將反覆運算器撰寫為方法,但編譯程式會將它轉譯成巢狀類別,也就是實際上的狀態機器。 只要客戶端程式代碼中的循環繼續,這個類別就會追蹤反覆運算器 For Each...Next 的位置。
若要查看編譯程式的功能,您可以使用 Ildasm.exe 工具來檢視反覆運算器方法所產生的通用中繼語言程式代碼。
當您為 類別 或 結構建立反覆運算器時,您不需要實作整個 IEnumerator 介面。 當編譯程式偵測到反覆運算器時,它會自動產生 Current或 MoveNext 介面的 Dispose 、 IEnumerator和 IEnumerator<T> 方法。
在每次迭代的For Each…Next迴圈(或直接呼叫IEnumerator.MoveNext)中,下一個迭代器的程式碼主體會在前一個Yield語句之後續行執行。 接著它會繼續到下一個 Yield 語句,直到到達反覆運算器主體的結尾,或遇到 Exit Function 或 Return 語句為止。
反覆運算器不支援 IEnumerator.Reset 方法。 若要從頭重新反覆運算,您必須取得新的反覆運算器。
如需詳細資訊,請參閱 Visual Basic語言規格。
使用迭代器
當您需要使用複雜的程式代碼來填入清單序列時,迭代器可讓您在循環中保持 For Each 的簡單性。 當您想要執行下列動作時,這非常有用:
在第一次
For Each迴圈迭代次數後修改清單順序。避免在第一次迭代
For Each迴圈之前完整載入大型清單。 例如,分頁提取以載入一批表格行。 另一個範例是 EnumerateFiles 方法,它會在 .NET Framework 內實作反覆運算器。在迭代器中封裝列表的建立。 在反覆運算器方法中,您可以建置清單,然後在迴圈中產生每個結果。