迭代器 (Visual Basic)
「迭代器」可用來逐步執行集合,例如清單和陣列。
迭代器方法或 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 中引進。
注意
如需本文中除簡易迭代器範例以外的其他所有範例,請包含 System.Collections
和 System.Collections.Generic
命名空間的 Imports 陳述式。
簡單的 Iterator
下列範例在 For…Next 迴圈內有一行 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
類別,其中包含動物的集合。
參考類別執行個體 (theZoo
) 的 For Each
陳述式會隱含呼叫 GetEnumerator
方法。 參考 Birds
和 Mammals
屬性的 For Each
陳述式會使用 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
Try 區塊
Visual Basic 允許在 Try...Catch...Finally Statement 的 Try
區塊中使用 Yield
陳述式。 具有 Yield
陳述式的 Try
區塊可以有 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 工具來檢視為迭代器方法產生的通用中間語言程式碼。
當您建立 class 或 struct 的迭代器時,您不需要實作整個 IEnumerator 介面。 當編譯器偵測到迭代器時,它會自動產生 IEnumerator 或 IEnumerator<T> 介面的 Current
、MoveNext
和 Dispose
方法。
之後每次反覆運算 For Each…Next
迴圈 (或直接呼叫 IEnumerator.MoveNext
),下一個迭代器程式碼主體都會在上一個 Yield
陳述式之後繼續。 然後繼續執行至下一個 Yield
陳述式,直到達到迭代器主體結尾,或遇到 Exit Function
或 Return
陳述式為止。
迭代器不支援 IEnumerator.Reset 方法。 若要從頭開始逐一查看,您必須取得新的迭代器。
如需其他資訊,請參閱 Visual Basic 語言規格。
迭代器的使用
當您需要使用複雜的程式碼來填入清單序列時,迭代器可讓您維持 For Each
迴圈的簡潔性。 當您想要執行下列作業時,這會很有用:
在第一次反覆運算
For Each
迴圈之後修改清單序列。避免在第一次反覆運算
For Each
迴圈之前完整載入大型清單。 分頁擷取以分批載入資料表資料列即為一例。 另一個範例是 EnumerateFiles 方法,它會在 .NET Framework 中實作迭代器。在迭代器中封裝建立清單。 在迭代器方法中,您可以建立清單,然後在迴圈中產生每個結果。