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

Try ~ Catch與 Iterator ~ Yield

程式裡不可能沒有 Try ~ Catch,我們看看 Iterator 與 Yield 在 Try ~ Catch 裡的使用。

Module Module1
    ''' <summary>
    ''' 測試 Iterator 與 Yield 與 Try ~ Catch
    ''' </summary>
    Private Iterator Function TestTryCatch() As IEnumerable(Of Integer)
        Try
            Yield 3
            Yield 4
            Throw New Exception("出事了!")
            Yield 5
            Yield 6
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        Finally
            Console.WriteLine("Finally 已被執行!")
        End Try
    End Function

    Sub Main()
        For Each number As Integer In TestTryCatch()
            Console.WriteLine(number)
        Next
        Console.ReadLine()

        ' 輸出:
        ' 3
        ' 4
        ' 出事了!
        ' Finally 已被執行!
    End Sub
End Module

你可以發現,執行至 Throw New Exception() 之後,因為進入的 Catch 然後執行至 Finally,程式就結束。也就是說,當程式執行至一半擲出例外(Catch),整個 Iterator Function 就會停止不在進行。而 Finally 是不管有沒有發生例外,都還是會被執行。這很符合我們的常理。

匿名方法(Anonymous Methods)與 Iterator ~ Yield

Module Module1
    Sub Main()
        Dim iterateSequence = Iterator Function() As IEnumerable(Of Integer)
                                  Yield 1
                                  Yield 2
                                  Yield 3
                              End Function

        ' 呼叫匿名方法
        For Each number As Integer In iterateSequence()
            Console.Write(number & " ")
        Next
        Console.ReadLine()

        ' 輸出: 1 2 3
    End Sub
End Module

與之前差異不大,透過 Iterator 與 Yield 的幫忙,讓匿名方法可以擁有舉列的能力。我們再舉一個範例:

Module Module1
    ''' <summary>
    ''' 取得數字的順序
    ''' </summary>
    ''' <param name="low">最小不得小於1</param>
    ''' <param name="high">最大不得大於100</param>
    ''' <returns>有順序數字</returns>
    Function GetSequence(low As Integer,
                         high As Integer) As IEnumerable

        ' 驗證參數
        If low <= 1 Then
            Throw New ArgumentException("low is too low")
        End If

        If high >= 100 Then
            Throw New ArgumentException("high is too high")
        End If

        ' 透過可舉列的匿名方法建立順序
        Dim iterateSequence = Iterator Function() As IEnumerable
                                  For index = low To high
                                      Yield index
                                  Next
                              End Function

        Return iterateSequence()
    End Function

    Sub Main()
        For Each number As Integer In GetSequence(38, 43)
            Console.Write(number & " ")
        Next
        Console.ReadLine()

        ' 輸出: 38 39 40 41 42 43
    End Sub
End Module

這裡注意驗證參數的部份,如果你把驗證參數寫在匿名方法裡,那必須等到 For Each 第一次去呼叫 Iterator 時,才會開始發生作用。在 Iterator 的說明文件中有一段話:「Iterators 是LINQ查詢中延後執行行為的基礎。」又學到一招。【註】在 MSDN 說明中「在 C# 裡 yield 陳述式不能出現於匿名方法中。」

泛型集合(Generic List)與 Iterator ~ Yield

下面這個範例,是 Stack(Of T)泛型類別實作 IEnumerable(Of T)泛型介面。


Imports System
Imports System.Collections

''' <summary>
''' Stack 泛型類別,實作 IEnumerable(Of T)介面
''' </summary>
''' <typeparam name="T">型別</typeparam>
''' <remarks></remarks>
Class Stack(Of T)
    Implements IEnumerable(Of T)

    ' 最大陣列數100個
    Private values() As T = New T(99) {}
    ' 最前最大筆數
    Private top As Integer = 0

    ''' <summary>
    ''' Push 方法,將值放入 values() 陣列之中
    ''' </summary>
    ''' <param name="t">型別</param>
    Sub Push(t As T)
        values(top) = t
        top = top + 1
    End Sub

    ''' <summary>
    ''' Pop方法,取出最上面一個值
    ''' </summary>
    ''' <returns>values()陣列最後一個值</returns>
    Function Pop() As T
        top = top - 1
        Return values(top)
    End Function

    ''' <summary>
    ''' 實作 IEnumerable(Of T).GetEnumerator,讓執行個體能被 For Each 使用
    ''' </summary>
    ''' <returns>由高至低values()陣列列舉</returns>
    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

    ''' <summary>
    ''' 注意,實作 IEnumerable.GetEnumerator 非泛型方法,但呼叫實作 GetEnumerator() 方法
    ''' </summary>
    Iterator Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
        Yield GetEnumerator()
    End Function

    ''' <summary>
    ''' 由高至低順序
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property TopToBottom() As IEnumerable(Of T)
        Get
            Return Me
        End Get
    End Property

    ''' <summary>
    ''' 由低至高順序,注意,這裡使用 Iterator ~ Yield
    ''' </summary>
    ''' <returns>由低至高values()陣列列舉</returns>
    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

    ''' <summary>
    ''' 取出最前面幾筆
    ''' </summary>
    ''' <param name="itemsFromTop">筆數</param>
    ''' <returns>values()陣列列舉前幾筆</returns>
    Iterator Function TopN(itemsFromTop As Integer) As IEnumerable(Of T)
        ' 取得開始 Index
        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

注意,除泛型 IEnumerable(Of T).GetEnumerator 方法實作,非泛型 GetEnumerator 方法也必須一起實作,這是因為IEnumerable(Of T)是從 IEnumerable 介面繼承而來。非泛型的實作會服從泛型實作。

我們來看主程式:

Module Module1
    Sub Main()
        Dim stk As New Stack(Of Integer)

        ' 新增項目至 Stack 泛型
        For number As Integer = 0 To 9
            stk.Push(number)
        Next

        ' 從 Stack 泛型,取出項目值
        ' 能夠 For Each 是因為 stk 有實作 IEnumerable(Of Integer)
        For Each number As Integer In stk
            Console.Write("{0} ", number)
        Next
        Console.WriteLine()
        Console.ReadLine()
        ' 輸出: 9 8 7 6 5 4 3 2 1 0

        ' 能夠 For Each 是因為 stk.TopToBottom 回傳 IEnumerable(Of Integer)
        For Each number As Integer In stk.TopToBottom
            Console.Write("{0} ", number)
        Next
        Console.WriteLine()
        Console.ReadLine()
        ' 輸出: 9 8 7 6 5 4 3 2 1 0

        For Each number As Integer In stk.BottomToTop
            Console.Write("{0} ", number)
        Next
        Console.WriteLine()
        Console.ReadLine()
        ' 輸出: 0 1 2 3 4 5 6 7 8 9 

        For Each number As Integer In stk.TopN(7)
            Console.Write("{0} ", number)
        Next
        Console.WriteLine()
        Console.ReadLine()
        ' 輸出: 9 8 7 6 5 4 3
    End Sub
End Module

1 則留言:

  1. 大大,我不認識你,但是看到你的技術文章整理的很詳細,想必是個對生活、專業很認真的人,給你拍拍手。

    回覆刪除

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