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