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
大大,我不認識你,但是看到你的技術文章整理的很詳細,想必是個對生活、專業很認真的人,給你拍拍手。
回覆刪除