迭代器 (Visual Basic)

「迭代器」可用來逐步執行集合,例如清單和陣列。

迭代器方法或 get 存取子會對集合執行自訂反覆運算。 反覆運算器方法會使用 Yield 語句一次傳回每個元素。 當到達 Yield 陳述式時,系統會記住程式碼中的目前位置。 下次呼叫迭代器函式時,便會從這個位置重新開始執行。

您可以使用 For Each... 從用戶端程式代碼取用反覆運算器 ...下一個 語句,或使用 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 存取子的傳回型別可以是 IEnumerableIEnumerable<T>IEnumeratorIEnumerator<T>

您可以使用 Exit FunctionReturn 語句結束反復專案。

Visual Basic反覆運算器函式或 get 存取子宣告包含Iterator修飾詞。

反覆運算器是在 Visual Studio 2012 Visual Basic 引進。

本主題內容

注意

對於主題中的所有範例,除了 Simple Iterator 範例之外,請包含 和 System.Collections.Generic 命名空間的 System.CollectionsImports語句。

簡單的 Iterator

下列範例有一個位於 For... 內的單 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 方法。 參考 BirdsMammals 屬性的 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

試用區塊

Visual Basic允許 Yield Try... 區塊中的 Try 語句...抓住。。。Finally 語句Try具有 語句的 Yield 區塊可以有 Catch 區塊,而且可以有 Finally 區塊。

下列範例會在 Try 反覆運算器函式中包含 、 CatchFinally 區塊。 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語句不能位於區塊或 Finally 區塊內 Catch

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。 非泛型實作會延後到泛型實作。

此範例使用具名迭代器來支援逐一查看相同資料集合的各種方法。 這些具名迭代器是 TopToBottomBottomToTop 屬性,以及 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」 不是保留字,而且只有在方法或 get 存取子中使用 Iterator 時,才會有特殊意義。

技術實作

雖然您將迭代器撰寫成方法,但編譯器會將其轉譯成巢狀類別,其實也就是狀態機器。 此類別會在用戶端程式碼中的 For Each...Next 迴圈繼續期間追蹤迭代器的位置。

若要查看編譯器的功能,您可以使用 Ildasm.exe 工具來檢視為迭代器方法產生的 Microsoft 中繼語言程式碼。

當您為 類別結構建立反覆運算器時,您不需要實作整個 IEnumerator 介面。 當編譯器偵測到迭代器時,它會自動產生 IEnumeratorIEnumerator<T> 介面的 CurrentMoveNextDispose 方法。

之後每次反覆運算 For Each…Next 迴圈 (或直接呼叫 IEnumerator.MoveNext),下一個迭代器程式碼主體都會在上一個 Yield 陳述式之後繼續。 接著,它會繼續到下一個 Yield 語句,直到到達反覆運算器主體的結尾,或直到 Exit Function 遇到 或 Return 語句為止。

反覆運算器不支援 IEnumerator.Reset 方法。 若要從頭開始逐一查看,您必須取得新的迭代器。

如需詳細資訊,請參閱Visual Basic語言規格

迭代器的使用

當您需要使用複雜的程式碼來填入清單序列時,迭代器可讓您維持 For Each 迴圈的簡潔性。 當您想要執行下列作業時,這會很有用:

  • 在第一次反覆運算 For Each 迴圈之後修改清單序列。

  • 避免在第一次反覆運算 For Each 迴圈之前完整載入大型清單。 分頁擷取以分批載入資料表資料列即為一例。 另一個範例是 EnumerateFiles 方法,它會在 .NET Framework 中實作迭代器。

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

另請參閱