VB.NET Try Catch Finally 筆記(基礎篇)
Try...Catch...Finally 是 VB.NET 用來處理例外狀況的語法。當程式執行檔案讀取、數值轉換、資料存取、網路連線或任何可能失敗的流程時,例外處理可以避免程式直接中斷,並讓畫面顯示可理解的處理結果。
例外處理的重點不是把錯誤藏起來,而是把「可能失敗的流程」和「失敗後要怎麼處理」分清楚。能事前判斷的狀況先用 If 檢查;真正無法完全預測的執行期失敗,再交給 Try...Catch 處理。
先理解例外處理在做什麼
例外(Exception):程式執行時發生的非預期狀況,例如檔案不存在、資料格式錯誤、權限不足、除以零、路徑不合法、檔案被其他程式占用。若沒有處理,程式可能會中斷或顯示難以理解的錯誤訊息。
Try、Catch、Finally 的角色
- Try:放入可能發生例外的程式碼。
- Catch:發生指定例外時,執行對應處理。
- Finally:不論成功或失敗,最後都會執行的清理流程。
- Throw:主動丟出例外,或在處理後重新丟出例外。
- Exception 物件:保存錯誤類型、錯誤訊息與呼叫堆疊等資訊。
| 區塊 | 用途 | 實務理解 |
|---|---|---|
| Try | 執行主要流程。 | 可能失敗,但仍需要嘗試執行。 |
| Catch | 攔截例外。 | 把錯誤轉成可理解的處理結果。 |
| Finally | 最後清理。 | 關閉資源、恢復按鈕、更新狀態。 |
| Throw | 丟出例外。 | 流程無法繼續時,把問題交給外層處理。 |
基本語法與執行順序
基本結構
Try
' 可能發生例外的程式碼
Catch ex As Exception
' 發生例外時的處理
Finally
' 不論成功或失敗都會執行
End Try
執行順序:程式會先進入 Try。若沒有發生例外,會略過 Catch,最後執行 Finally。若 Try 裡發生例外,會跳到符合的 Catch,處理完成後仍會執行 Finally。
不要用 Try Catch 取代基本判斷
像文字框是否空白、數字是否大於 0、索引是否在範圍內,這些可以事前判斷的情況,應先用 If 或 TryParse 檢查。Try...Catch 應用在執行過程中仍可能失敗的流程,例如檔案被占用、權限不足、路徑突然不存在。
數值轉換與格式錯誤
場景一:包裝材料費用試算
這個範例示範格式錯誤的處理。雖然可以用 Try...Catch 攔截 FormatException,但更推薦使用 Decimal.TryParse 先檢查輸入,讓錯誤流程更清楚。
需要的主控項
TextBoxBoxCount:輸入紙盒數量。TextBoxBoxPrice:輸入單個紙盒費用。ButtonCalculateBox:計算費用。LabelBoxResult:顯示結果。
範例程式碼
Public Class Form1
Private Sub ButtonCalculateBox_Click(sender As Object, e As EventArgs) Handles ButtonCalculateBox.Click
Try
Dim boxCount As Integer = Integer.Parse(TextBoxBoxCount.Text.Trim())
Dim boxPrice As Decimal = Decimal.Parse(TextBoxBoxPrice.Text.Trim())
If boxCount <= 0 OrElse boxPrice < 0D Then
LabelBoxResult.Text = "數量必須大於 0,單價不可小於 0"
Return
End If
Dim totalAmount As Decimal = boxCount * boxPrice
LabelBoxResult.Text = "材料費用:" & totalAmount.ToString("N0") & " 元"
Catch ex As FormatException
LabelBoxResult.Text = "輸入格式錯誤,請輸入數字"
Catch ex As OverflowException
LabelBoxResult.Text = "輸入數值太大,無法計算"
End Try
End Sub
End Class
邏輯解析
Integer.Parse與Decimal.Parse在格式錯誤時會丟出例外。FormatException代表文字無法轉成指定數值型別。OverflowException代表數值超出型別可容納範圍。- 實務上若只是處理文字框輸入,通常優先使用
TryParse,比用例外控制流程更穩定。
較推薦的輸入檢查寫法
Dim boxCount As Integer
If Not Integer.TryParse(TextBoxBoxCount.Text.Trim(), boxCount) Then
LabelBoxResult.Text = "請輸入正確數量"
Return
End If
TryParse 適合處理「預期可能輸入錯誤」的情境;Try...Catch 適合處理「執行時仍可能突然失敗」的情境。
檔案讀取與特定例外
場景二:讀取班表文字檔
檔案讀取即使事前檢查路徑,仍可能因為權限、檔案被占用、磁碟狀態或路徑變更而失敗。因此檔案流程很適合搭配 Try...Catch 與特定例外處理。
需要的主控項
TextBoxSchedulePath:輸入班表檔案路徑。TextBoxScheduleContent:顯示班表內容,建議設定Multiline=True。ButtonReadSchedule:讀取班表。LabelScheduleStatus:顯示狀態。
範例程式碼
Imports System.IO
Imports System.Text
Public Class Form1
Private Sub ButtonReadSchedule_Click(sender As Object, e As EventArgs) Handles ButtonReadSchedule.Click
Dim filePath As String = TextBoxSchedulePath.Text.Trim()
If filePath = String.Empty Then
LabelScheduleStatus.Text = "請輸入班表檔案路徑"
Return
End If
Try
TextBoxScheduleContent.Text = File.ReadAllText(filePath, Encoding.UTF8)
LabelScheduleStatus.Text = "班表讀取完成"
Catch ex As FileNotFoundException
LabelScheduleStatus.Text = "檔案不存在,請確認路徑與檔名"
Catch ex As DirectoryNotFoundException
LabelScheduleStatus.Text = "資料夾不存在,請確認路徑"
Catch ex As UnauthorizedAccessException
LabelScheduleStatus.Text = "權限不足,無法讀取檔案"
Catch ex As IOException
LabelScheduleStatus.Text = "檔案目前可能被其他程式使用"
Catch ex As Exception
LabelScheduleStatus.Text = "讀取失敗:" & ex.GetType().Name
End Try
End Sub
End Class
邏輯解析
- 不同例外代表不同失敗原因,分開處理可讓訊息更精準。
- 較具體的
Catch應放在前面,較廣泛的Catch ex As Exception放最後。 Catch ex As Exception適合當最後防線,不適合取代所有特定錯誤處理。
Finally:不論成功失敗都要執行
Finally:不論 Try 成功完成,或中途發生例外進入 Catch,最後都會執行。常用於恢復按鈕狀態、關閉等待提示、釋放資源、寫入結束紀錄。
場景三:匯入名單時鎖定按鈕
匯入流程開始時先停用按鈕,避免連續按下造成重複執行。不論匯入成功或失敗,最後都應把按鈕恢復可用,這種清理動作適合放在 Finally。
需要的主控項
TextBoxImportFile:輸入名單檔案路徑。ButtonImportNames:匯入名單。ListBoxImportNames:顯示匯入結果。LabelImportStatus:顯示狀態。
範例程式碼
Imports System.IO
Imports System.Text
Public Class Form1
Private Sub ButtonImportNames_Click(sender As Object, e As EventArgs) Handles ButtonImportNames.Click
ButtonImportNames.Enabled = False
LabelImportStatus.Text = "名單匯入中..."
ListBoxImportNames.Items.Clear()
Try
Dim filePath As String = TextBoxImportFile.Text.Trim()
Dim lines() As String = File.ReadAllLines(filePath, Encoding.UTF8)
For Each line As String In lines
Dim nameText As String = line.Trim()
If nameText <> String.Empty Then
ListBoxImportNames.Items.Add(nameText)
End If
Next
LabelImportStatus.Text = "匯入完成,筆數:" & ListBoxImportNames.Items.Count.ToString()
Catch ex As Exception
LabelImportStatus.Text = "匯入失敗:" & ex.GetType().Name
Finally
ButtonImportNames.Enabled = True
End Try
End Sub
End Class
邏輯解析
- 按鈕在流程開始時停用,可避免重複觸發。
- 無論讀檔成功或失敗,
Finally都會把按鈕恢復可用。 Finally適合放「一定要做」的收尾動作。
Throw:主動丟出例外
場景四:優惠券規則檢查
當資料不符合業務規則,而且目前方法無法繼續完成工作時,可以主動 Throw 例外。外層呼叫端再決定要顯示訊息、寫入紀錄或停止流程。
需要的主控項
TextBoxCouponCode:輸入優惠券代碼。TextBoxOrderAmount:輸入訂單金額。ButtonApplyCoupon:套用優惠券。LabelCouponResult:顯示結果。
範例程式碼
Public Class Form1
Private Sub ButtonApplyCoupon_Click(sender As Object, e As EventArgs) Handles ButtonApplyCoupon.Click
Try
Dim amount As Decimal
If Not Decimal.TryParse(TextBoxOrderAmount.Text.Trim(), amount) Then
LabelCouponResult.Text = "訂單金額格式錯誤"
Return
End If
Dim discount As Decimal = CalculateCouponDiscount(TextBoxCouponCode.Text.Trim(), amount)
LabelCouponResult.Text = "折抵金額:" & discount.ToString("N0") & " 元"
Catch ex As InvalidOperationException
LabelCouponResult.Text = ex.Message
End Try
End Sub
Private Function CalculateCouponDiscount(couponCode As String, amount As Decimal) As Decimal
If couponCode = String.Empty Then
Throw New InvalidOperationException("請輸入優惠券代碼")
End If
If amount < 500D Then
Throw New InvalidOperationException("訂單金額未達優惠券門檻")
End If
If couponCode.ToUpper() = "WELCOME100" Then
Return 100D
End If
Throw New InvalidOperationException("優惠券代碼無效")
End Function
End Class
邏輯解析
Throw New InvalidOperationException(...)表示目前流程無法繼續。- 外層
Catch接住例外後,把業務訊息顯示在畫面上。 - 例外適合用在流程無法完成的狀況,不適合拿來做一般條件分支。
資源釋放:Using 與 Finally
資源型物件:像 StreamReader、StreamWriter、資料庫連線、檔案串流等物件,用完後需要釋放。這類物件通常實作 IDisposable,可用 Using 自動釋放。
場景五:逐行讀取出貨紀錄
大型檔案不適合一次 ReadAllText 讀入。這個範例使用 StreamReader 逐行讀取,並用 Using 確保讀取器最後會被關閉。
需要的主控項
TextBoxShippingLogPath:輸入出貨紀錄檔路徑。ButtonReadShippingLog:讀取紀錄。ListBoxShippingLog:顯示前 20 筆紀錄。LabelShippingStatus:顯示狀態。
範例程式碼
Imports System.IO
Imports System.Text
Public Class Form1
Private Sub ButtonReadShippingLog_Click(sender As Object, e As EventArgs) Handles ButtonReadShippingLog.Click
ListBoxShippingLog.Items.Clear()
Try
Dim filePath As String = TextBoxShippingLogPath.Text.Trim()
Dim readCount As Integer = 0
Using reader As New StreamReader(filePath, Encoding.UTF8)
Do While Not reader.EndOfStream AndAlso readCount < 20
Dim line As String = reader.ReadLine()
ListBoxShippingLog.Items.Add(line)
readCount += 1
Loop
End Using
LabelShippingStatus.Text = "讀取完成,顯示筆數:" & readCount.ToString()
Catch ex As Exception
LabelShippingStatus.Text = "讀取失敗:" & ex.GetType().Name
End Try
End Sub
End Class
邏輯解析
Using區塊結束時會自動釋放StreamReader。- 即使讀取途中發生例外,
Using仍會處理資源釋放。 - 資源釋放優先考慮
Using;需要恢復畫面狀態時,再使用Finally。
綜合應用:匯入訂單檔案
場景六:訂單 CSV 匯入檢查
這個範例整合輸入檢查、檔案讀取、逐行解析、特定例外、業務規則例外與最後狀態恢復。流程會讀取 CSV,每一行格式為 品名,數量,單價。
需要的主控項
TextBoxOrderCsvPath:輸入 CSV 檔案路徑。ButtonImportOrders:匯入訂單。ListBoxOrderItems:顯示匯入項目。LabelOrderImportStatus:顯示匯入狀態。LabelOrderImportSummary:顯示摘要。
範例程式碼
Imports System.IO
Imports System.Text
Public Class OrderLine
Public Property ItemName As String
Public Property Quantity As Integer
Public Property UnitPrice As Decimal
Public Sub New(itemName As String, quantity As Integer, unitPrice As Decimal)
Me.ItemName = itemName
Me.Quantity = quantity
Me.UnitPrice = unitPrice
End Sub
Public ReadOnly Property Amount As Decimal
Get
Return Quantity * UnitPrice
End Get
End Property
Public Function ToDisplayText() As String
Return ItemName & " / 數量:" & Quantity.ToString() &
" / 小計:" & Amount.ToString("N0")
End Function
End Class
Public Class Form1
Private Sub ButtonImportOrders_Click(sender As Object, e As EventArgs) Handles ButtonImportOrders.Click
ButtonImportOrders.Enabled = False
ListBoxOrderItems.Items.Clear()
LabelOrderImportStatus.Text = "匯入中..."
LabelOrderImportSummary.Text = String.Empty
Try
Dim filePath As String = TextBoxOrderCsvPath.Text.Trim()
If filePath = String.Empty Then
Throw New InvalidOperationException("請輸入訂單檔案路徑")
End If
Dim orderLines As New List(Of OrderLine)()
Dim rowNumber As Integer = 0
Using reader As New StreamReader(filePath, Encoding.UTF8)
Do While Not reader.EndOfStream
rowNumber += 1
Dim lineText As String = reader.ReadLine()
If lineText.Trim() = String.Empty Then
Continue Do
End If
orderLines.Add(ParseOrderLine(lineText, rowNumber))
Loop
End Using
Dim totalAmount As Decimal = 0D
For Each item As OrderLine In orderLines
ListBoxOrderItems.Items.Add(item.ToDisplayText())
totalAmount += item.Amount
Next
LabelOrderImportStatus.Text = "匯入完成"
LabelOrderImportSummary.Text = "筆數:" & orderLines.Count.ToString() &
" / 總額:" & totalAmount.ToString("N0")
Catch ex As FileNotFoundException
LabelOrderImportStatus.Text = "檔案不存在"
Catch ex As UnauthorizedAccessException
LabelOrderImportStatus.Text = "權限不足,無法讀取檔案"
Catch ex As IOException
LabelOrderImportStatus.Text = "檔案讀取失敗,可能正在被使用"
Catch ex As InvalidOperationException
LabelOrderImportStatus.Text = ex.Message
Catch ex As Exception
LabelOrderImportStatus.Text = "匯入失敗:" & ex.GetType().Name
Finally
ButtonImportOrders.Enabled = True
End Try
End Sub
Private Function ParseOrderLine(lineText As String, rowNumber As Integer) As OrderLine
Dim parts() As String = lineText.Split(","c)
If parts.Length <> 3 Then
Throw New InvalidOperationException("第 " & rowNumber.ToString() & " 行欄位數量錯誤")
End If
Dim itemName As String = parts(0).Trim()
Dim quantity As Integer
Dim unitPrice As Decimal
If itemName = String.Empty Then
Throw New InvalidOperationException("第 " & rowNumber.ToString() & " 行品名不可空白")
End If
If Not Integer.TryParse(parts(1).Trim(), quantity) OrElse quantity <= 0 Then
Throw New InvalidOperationException("第 " & rowNumber.ToString() & " 行數量格式錯誤")
End If
If Not Decimal.TryParse(parts(2).Trim(), unitPrice) OrElse unitPrice < 0D Then
Throw New InvalidOperationException("第 " & rowNumber.ToString() & " 行單價格式錯誤")
End If
Return New OrderLine(itemName, quantity, unitPrice)
End Function
End Class
整合重點
- 主要匯入流程放在
Try裡。 - 檔案相關錯誤使用特定
Catch區分原因。 - 資料格式與業務規則錯誤由
InvalidOperationException提供清楚訊息。 Using負責釋放檔案讀取器,Finally負責恢復按鈕狀態。
實務判斷與常見誤區
常見問題整理
- 把所有程式碼包進同一個 Try:會讓錯誤來源不清楚,應只包住可能失敗的區段。
- 只寫 Catch ex As Exception:可當最後防線,但重要流程應先處理特定例外。
- 把錯誤完全吞掉:空的
Catch會讓問題消失在表面,後續更難追查。 - 用例外處理一般輸入檢查:文字框轉數字通常優先使用
TryParse。 - 在 Catch 裡使用 Throw ex:這會重設堆疊資訊;需要重新丟出時通常使用
Throw。 - 忘記清理資源:檔案、串流、連線應使用
Using或確保最後釋放。 - 只顯示原始錯誤訊息:畫面訊息應轉成可理解的處理方向,詳細錯誤可另外寫入紀錄。
| 需求 | 建議寫法 | 原因 |
|---|---|---|
| 檢查輸入格式 | TryParse |
輸入錯誤是可預期情境,不必依賴例外。 |
| 讀寫檔案 | Try...Catch |
檔案可能不存在、被占用或權限不足。 |
| 釋放串流資源 | Using |
區塊結束後自動釋放資源。 |
| 恢復畫面狀態 | Finally |
成功或失敗都應執行。 |
| 流程不能繼續 | Throw |
讓外層統一決定如何處理。 |
重點整理
Try...Catch...Finally用來處理執行期間可能發生的例外狀況。Try放主要流程,Catch放失敗處理,Finally放最後一定要執行的清理流程。- 可以事前判斷的錯誤,應先用
If或TryParse處理。 - 特定例外應放在前面,
Catch ex As Exception放在最後。 Finally適合恢復按鈕、關閉提示、釋放或收尾。- 檔案、串流、連線等資源型物件,優先使用
Using管理生命週期。 Throw可主動丟出例外;重新丟出目前例外時通常使用Throw,不要使用Throw ex。- 例外處理不是隱藏錯誤,而是讓失敗流程可控、可理解、可維護。