Visual Basic 11 的 Iterator 與 Yield 心得筆記(2)

前一篇「Iterator 與 Yield 心得(1)」我們了解了 IEnumerable介面與 IEnumerator介面的關係,實作 IEnumerable介面的 GetEnumrator 方法可使型別變成可列舉的型別,還必須實作 IEnumerator介面裡的 MoveNext、Current、Reset等。

現在透過 VB 11 終於也可以有 C# 的方便性。使用 Iterator 與 Yield 我們也可以很方便實作出可舉列的型別或方法。我們先了解如何撰寫 Iterator 與 Yield 程式碼,然後在解釋整個 Iterator 與 Yield 的運作流程。

Module Module1
    Sub Main()
        Dim number As Integer = 0
        For Each number In SomeNumbers()
            Console.Write(number & " ")
        Next
        Console.ReadLine()

        ' 輸出:3 5 8
    End Sub

    ''' <summary>
    ''' Iterator 函式
    ''' </summary>
    Private Iterator Function SomeNumbers() As IEnumerable
        ' 使用多個 yield 關鍵字
        Yield 3
        Yield 5
        Yield 8
    End Function
End Module

我們來看 SomeNumbers():
  1. 在 Function 之前,必須加上 Iterator 關鍵字
  2. 回傳值使用 Yield 關鍵字,而非 Return
  3. C# 使用 yield break 陳述式來結束 Iterator,VB 11 使用 Exit Function 或 Return 來結束
  4. C# 使用 yield return,VB 11 直接使用 Yield
  5. Iterator 的回傳型別只能是 IEnumerable、IEnumerable(Of T)、IEnumerator、IEnumrator(Of T) 四種。
是否發現在使用 SomeNumbers() 時,你並不需要去實作整個 IEnumerable介面內容了。那是當 compiler 偵測到 Iterator 時,它會自動產生 IEnumerable 或 IEnumerable(Of T) 介面的 MoveNext、Current 注意的是,它不會產生 Reset 方法

我們來看在泛型裡的使用。


Module Module1
    Sub Main()
        Dim number As Integer = 0
        For Each number In EvenSequence(3, 19)
            Console.Write(number & " ")
        Next
        Console.ReadLine()

        ' 輸出:4 6 8 10 12 14 16 18
    End Sub

    ''' <summary>
    ''' Iterator 在泛型的使用
    ''' </summary>
    ''' <param name="firstNumber">開始數值</param>
    ''' <param name="lastNumber">結束數值</param>
    ''' <returns>偶數值</returns>
    Private Iterator Function EvenSequence(firstNumber As Integer,
                                           lastNumber As Integer) As IEnumerable(Of Integer)
        ' 回傳偶數
        For number As Integer = firstNumber To lastNumber
            If number Mod 2 = 0 Then
                Yield number
            End If
        Next
    End Function
End Module

在泛型裡使用 Iterator 沒有太大難處,與一般在 Function 裡使用一樣。如心得(1),本來 Yield number 那裡應該還再去實作一個類別,類別裡還要實作 IEnumerator介面,現在都省下來了。

我們再來看在類別裡的使用,這個例子比較像心得(1)範例與 Iterator 的結合:

Module Module1
    Sub Main()
        Dim days As New DaysOfTheWeek()

        For Each day As String In days
            Console.Write(day & " ")
        Next
        Console.ReadLine()

        ' 輸出:Sun-星期日 ...略...
    End Sub
End Module

''' <summary>
''' 在實作 IEnumerable.GetEnumerator 方法中使用 Iterator
''' </summary>
Class DaysOfTheWeek
    Implements IEnumerable

    Public days() As String = {"Sun-星期日", "Mon-星期一", "Tue-星期二",
                                "Wed-星期三", "Thr-星期四", "Fri-星期五", "Sat-星期六"}

    Iterator Function GetEnumerator() As IEnumerator Implements _
                                                IEnumerable.GetEnumerator
        For i As Integer = 0 To days.Length - 1
            Yield days(i)
        Next
    End Function
End Class

我們在一個實作 IEnumerable介面的類別裡直接使用 Iterator 來簡化工作,心得(1)相比,我們減少實作整個 IEnumerator介面,帶來很大的方便性。

沒有留言:

張貼留言

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。