2024年5月12日 星期日

1.VB.NET 基礎篇 筆記 - For Each 迴圈

VB.NET For Each 迴圈筆記(基礎篇)

VB.NET For Each 迴圈 筆記(基礎篇)

處理多筆資料時,常見需求包括逐筆列出內容、累計統計值、篩選符合條件的項目,以及將物件集合整理成可讀結果。VB.NET 的 For Each 迴圈就是用來處理這類「依序走訪集合」的語法。

For Each 的核心特性,在於由執行環境依序取出集合中的元素,不必自行撰寫索引變數、起始值、終止值與遞增邏輯。因此,當需求重點在「讀取資料內容」而不是「控制索引位置」時,通常會比傳統 For 更清楚。

核心觀念與運作方式

For Each 的讀取思維

若把集合視為一串已經排好的資料清單,For Each 的工作不是計算第幾筆,而是「依照既有順序把每一筆交給目前的迴圈變數」。

  • 傳統 For:偏重索引控制,適合依索引處理、倒序處理或指定步進值。
  • For Each:偏重資料本身,適合讀取、顯示、累計與條件判斷。

For Each 的三個關鍵優點:

  1. 語法較精簡:不需要撰寫 Count - 1i += 1 之類的控制條件。
  2. 可讀性較高:程式閱讀焦點會放在元素內容本身,而不是索引計算。
  3. 降低越界風險:由系統依據集合實際範圍逐筆列舉,可減少索引控制錯誤。

執行環境與測試配置

本篇範例統一使用 Windows Forms

以下範例皆以 Windows Forms(WF) 為前提。建立專案後,建議先在 Form1 上放置下列主控項,便於直接測試多個場景。

  • Button1:執行場景一。
  • Button2:執行場景二。
  • Button3:執行場景三。
  • Button4:執行 Exit For 範例。
  • Button5:執行 Continue For 範例。
  • Label1:顯示結果。建議 AutoSize = False,並適度放大寬高。

應用場景與語法實作

場景一:讀取陣列資料

陣列長度固定,適合示範 For Each 最基本的走訪方式。此範例將課程名稱依序輸出到 Label1

需要的主控項
  • Button1
  • Label1
範例程式碼
VB.NET / Windows Forms
Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim courseTitles() As String = {"資料整理", "表單輸出", "例外處理", "檔案讀寫"}
        Dim message As String = "基礎課程清單:" & vbCrLf

        For Each title As String In courseTitles
            message &= "• " & title & vbCrLf
        Next

        Label1.Text = message
    End Sub
End Class
畫面輸出結果(Label1.Text)
基礎課程清單: • 資料整理 • 表單輸出 • 例外處理 • 檔案讀寫
邏輯解析
  • courseTitles() 建立字串陣列,資料筆數在宣告時已固定。
  • For Each title As String In courseTitles 代表逐筆取出陣列元素。
  • 每次迴圈處理的重點是 title 的內容,不是索引位置。
  • message &= ... 逐步累加要顯示的結果文字。

場景二:讀取 List 集合並加入流水號

List(Of T) 常用在筆數不固定的資料集合。雖然 For Each 不直接提供索引,但仍可自行額外建立編號變數。

需要的主控項
  • Button2
  • Label1
範例程式碼
VB.NET / Windows Forms
Public Class Form1
    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Dim queueNames As New List(Of String) From {
            "上午批次匯入",
            "報表彙整程序",
            "通知郵件發送",
            "夜間資料封存"
        }

        Dim serialNo As Integer = 1
        Dim message As String = "工作佇列:" & vbCrLf

        For Each queueName As String In queueNames
            message &= serialNo.ToString("00") & ". " & queueName & vbCrLf
            serialNo += 1
        Next

        Label1.Text = message
    End Sub
End Class
畫面輸出結果(Label1.Text)
工作佇列: 01. 上午批次匯入 02. 報表彙整程序 03. 通知郵件發送 04. 夜間資料封存
邏輯解析
  • List(Of String) 適合筆數可能增減的文字資料。
  • serialNo 不是集合索引,而是額外設計的顯示編號。
  • ToString("00") 用來輸出固定兩位數格式。
  • 若後續需要真正索引位置,仍建議改用傳統 For

場景三:讀取自訂物件集合並累計統計值

實務資料通常不是單一字串,而是包含多個欄位的物件。For Each 讀取物件集合時,可直接使用屬性完成列印與累計。

需要的主控項
  • Button3
  • Label1
範例程式碼
VB.NET / Windows Forms
Public Class DeviceRecord
    Public Property DeviceName As String
    Public Property UnitCost As Decimal
    Public Property Keeper As String

    Public Sub New(name As String, cost As Decimal, keeperName As String)
        DeviceName = name
        UnitCost = cost
        Keeper = keeperName
    End Sub
End Class

Public Class Form1
    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        Dim devices As New List(Of DeviceRecord) From {
            New DeviceRecord("條碼掃描器", 6800D, "倉儲課"),
            New DeviceRecord("工業平板", 24500D, "製造課"),
            New DeviceRecord("標籤列印機", 13200D, "物流課")
        }

        Dim totalCost As Decimal = 0D
        Dim message As String = "設備清單:" & vbCrLf & vbCrLf

        For Each device As DeviceRecord In devices
            totalCost += device.UnitCost
            message &= "【" & device.DeviceName & "】 " &
                       "$" & device.UnitCost.ToString("N0") &
                       " / 保管單位:" & device.Keeper & vbCrLf
        Next

        message &= vbCrLf & "總筆數:" & devices.Count.ToString() & vbCrLf
        message &= "總金額:$" & totalCost.ToString("N0")

        Label1.Text = message
    End Sub
End Class
畫面輸出結果(Label1.Text)
設備清單: 【條碼掃描器】 $6,800 / 保管單位:倉儲課 【工業平板】 $24,500 / 保管單位:製造課 【標籤列印機】 $13,200 / 保管單位:物流課 總筆數:3 總金額:$44,500
邏輯解析
  • DeviceRecord 封裝設備名稱、金額與保管單位三個欄位。
  • For Each device As DeviceRecord In devices 逐筆讀取物件。
  • device.UnitCost 可直接參與統計,不需要先拆成其他集合。
  • 這種寫法很適合清單列印、總額計算與報表摘要。

使用限制與常見誤區

For Each 雖然適合讀取,但不適合在列舉過程中直接改變目前集合的結構,例如新增、刪除或移動元素。

InvalidOperationException 的典型原因

當迴圈正在列舉集合時,若同時呼叫 AddRemoveRemoveAt 改變集合結構,列舉器可能立即判定集合狀態已變更,進而拋出 InvalidOperationException

較安全的替代方式:傳統 For 倒序刪除
VB.NET
For i As Integer = queueNames.Count - 1 To 0 Step -1
    If queueNames(i).Contains("夜間") Then
        queueNames.RemoveAt(i)
    End If
Next
邏輯解析
  • Count - 1 取得最後一個有效索引。
  • Step -1 表示由尾端往前走訪。
  • 刪除元素後,後面資料會向前遞補;倒序處理可避免漏掉尚未檢查的元素。

進階流程控制

指令一:Exit For

當需求是尋找第一筆符合條件的資料時,找到目標後立即離開迴圈,可減少不必要的比對次數。

需要的主控項
  • Button4
  • Label1
VB.NET / Windows Forms
Public Class Form1
    Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
        Dim batchCodes() As String = {"BK-101", "BK-108", "BK-203", "BK-305"}
        Dim targetCode As String = "BK-203"
        Dim compareCount As Integer = 0

        For Each code As String In batchCodes
            compareCount += 1

            If code = targetCode Then
                Label1.Text = "找到批號:" & targetCode & vbCrLf &
                              "比對次數:" & compareCount.ToString()
                Exit For
            End If
        Next
    End Sub
End Class
畫面輸出結果(Label1.Text)
找到批號:BK-203 比對次數:3
邏輯解析
  • compareCount 用來觀察比對進行到第幾次。
  • Exit For 會立刻終止目前的迴圈。
  • 若目標出現在前段資料,這種寫法可縮短無效走訪。

指令二:Continue For

若某些資料不需要後續處理,可直接略過本輪剩餘程式,繼續下一筆。

需要的主控項
  • Button5
  • Label1
VB.NET / Windows Forms
Public Class Form1
    Private Sub Button5_Click(sender As Object, e As EventArgs) Handles Button5.Click
        Dim rawScores() As Integer = {88, -1, 76, 0, 94}
        Dim validIndex As Integer = 0
        Dim message As String = "有效成績:" & vbCrLf

        For Each score As Integer In rawScores
            If score <= 0 Then
                Continue For
            End If

            validIndex += 1
            message &= "第 " & validIndex.ToString() & " 筆:" & score.ToString() & vbCrLf
        Next

        Label1.Text = message
    End Sub
End Class
畫面輸出結果(Label1.Text)
有效成績: 第 1 筆:88 第 2 筆:76 第 3 筆:94
邏輯解析
  • score <= 0 視為無效資料,直接跳過。
  • Continue For 不會結束整個迴圈,只會略過本輪後續敘述。
  • 這種寫法常用於資料清洗與條件過濾。

For Each 與傳統 For 的適用情境比較

比較項目 For Each For
控制焦點 著重元素內容。 著重索引與次數控制。
語法複雜度 較精簡。 需自行管理起訖值與步進。
取得索引 無法直接取得。 可直接使用索引值。
修改集合結構 不適合。 較適合搭配倒序刪除。
常見用途 列印、顯示、統計、篩選。 索引比對、區間處理、刪除與重排。

重點整理

  1. For Each 適合逐筆讀取集合資料,不適合依賴索引控制的情境。
  2. 若需要刪除元素、倒序處理或指定步進值,優先考慮傳統 For
  3. Exit For 用於提早結束搜尋;Continue For 用於略過無效資料。
  4. 當集合元素是物件時,For Each 可直接結合屬性讀取與統計運算。