2024年6月14日 星期五

15.VB.NET 筆記 進階篇 - 列舉 (Enumeration)

VB.NET 列舉(Enumeration)筆記(基礎篇)

VB.NET 列舉(Enum) 筆記(基礎篇)

Enum 用來表示一組固定選項。比起在程式中到處使用數字或字串,列舉可以用清楚的名稱代表狀態、類型、等級或選項,讓程式更容易閱讀,也比較不容易把不相關的值混在一起。

列舉適合用在「選項固定、名稱有意義、程式會反覆判斷」的情境。這篇以 Windows Forms 範例說明基本宣告、指定數值、Select CaseComboBox、文字轉列舉與 <Flags> 組合列舉。

先理解 Enum 在解決什麼

Enum 是固定選項的命名集合

若某個欄位只會有固定幾種結果,例如座位狀態、取件時段、提醒方式、顯示模式,就很適合使用列舉。列舉的重點是讓程式看到名稱,而不是猜測數字或字串的意思。

  • 可讀性:SeatStatus.Reserved2 更容易理解。
  • 集中管理:固定選項集中宣告,不散落在表單事件中。
  • 型別限制:變數明確屬於某個列舉型別,不容易混入無關值。
  • 適合判斷:搭配 Select Case 可讓流程分支更清楚。

適合使用 Enum 的資料特徵:

  1. 選項固定:例如空位、已預約、使用中、暫停開放。
  2. 狀態有限:流程不會每天新增不同文字。
  3. 需要反覆判斷:多處程式都會根據狀態做不同處理。
做法 問題 適合程度
直接用數字 需要記住每個數字代表什麼。 只適合非常短且不公開的暫時計算。
直接用字串 容易拼錯,也不容易集中管理。 適合單純顯示,不適合流程判斷。
使用 Enum 需要先定義型別,但結構較清楚。 適合固定選項與狀態判斷。

宣告 Enum 與指定數值

基本語法

VB.NET
Public Enum 列舉名稱
    成員一
    成員二
    成員三
End Enum

未指定數值時,第一個成員預設為 0,後續依序遞增。若需要對應資料庫、外部代碼或固定規格,就應明確指定數值。

場景一:閱讀座位狀態

這個範例用閱讀座位狀態示範基本列舉。表單建立一個目前狀態,再依照列舉值顯示對應文字。

需要的主控項
  • ButtonShowSeat:顯示座位狀態。
  • LabelSeatStatus:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Enum SeatStatus
    Available
    Reserved
    InUse
    Closed
End Enum

Public Class Form1
    Private Sub ButtonShowSeat_Click(sender As Object, e As EventArgs) Handles ButtonShowSeat.Click
        Dim currentStatus As SeatStatus = SeatStatus.Reserved
        LabelSeatStatus.Text = "座位狀態:" & currentStatus.ToString()
    End Sub
End Class
畫面輸出結果(LabelSeatStatus.Text)
座位狀態:Reserved
邏輯解析
  • SeatStatus 把座位可能狀態集中定義。
  • currentStatus 的型別明確是 SeatStatus
  • ToString() 會輸出列舉成員名稱。

場景二:櫃位燈號指定代碼

列舉可以明確指定數值。當列舉值需要對應外部代碼、硬體狀態或資料庫欄位時,指定數值會比依賴預設遞增更穩定。

需要的主控項
  • ButtonShowLight:顯示燈號代碼。
  • LabelLight:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Enum CounterLightCode
    Off = 0
    Green = 10
    Yellow = 20
    Red = 30
End Enum

Public Class Form1
    Private Sub ButtonShowLight_Click(sender As Object, e As EventArgs) Handles ButtonShowLight.Click
        Dim light As CounterLightCode = CounterLightCode.Yellow
        LabelLight.Text = "燈號:" & light.ToString() & "|代碼:" & CInt(light).ToString()
    End Sub
End Class
畫面輸出結果(LabelLight.Text)
燈號:Yellow|代碼:20
邏輯解析
  • CounterLightCode.Yellow 對應整數值 20
  • CInt(light) 可把列舉值轉成整數。
  • 需要與外部代碼對應時,明確指定數值比較安全。

搭配 Select Case 進行判斷

場景三:取件時段提示文字

列舉很適合搭配 Select Case。每個 Case 都對應一個有名稱的列舉值,流程會比數字判斷清楚。

需要的主控項
  • ButtonShowPickup:顯示取件提示。
  • LabelPickup:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Enum PickupWindow
    Morning
    Noon
    Evening
    Weekend
End Enum

Public Class Form1
    Private Sub ButtonShowPickup_Click(sender As Object, e As EventArgs) Handles ButtonShowPickup.Click
        Dim window As PickupWindow = PickupWindow.Evening
        LabelPickup.Text = BuildPickupText(window)
    End Sub

    Private Function BuildPickupText(ByVal window As PickupWindow) As String
        Select Case window
            Case PickupWindow.Morning
                Return "取件時段:上午 09:00 - 11:30"
            Case PickupWindow.Noon
                Return "取件時段:中午 12:00 - 13:30"
            Case PickupWindow.Evening
                Return "取件時段:傍晚 17:30 - 20:00"
            Case PickupWindow.Weekend
                Return "取件時段:週末 10:00 - 16:00"
            Case Else
                Return "取件時段未設定"
        End Select
    End Function
End Class
畫面輸出結果(LabelPickup.Text)
取件時段:傍晚 17:30 - 20:00
邏輯解析
  • PickupWindow 表示固定取件時段。
  • Select Case 讓每個狀態對應一段清楚處理。
  • Case Else 可作為保護分支,避免未處理狀態沒有回應。

ComboBox 與字串轉列舉

場景四:櫃台服務區下拉選單

列舉可直接放進 ComboBox 當資料來源。讀取時再把 SelectedItem 轉回列舉型別,後續判斷就不用處理散亂字串。

需要的主控項
  • ComboBoxZone:選擇服務區。
  • ButtonReadZone:讀取選項。
  • LabelZone:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Enum ServiceZone
    FrontDesk
    ReadingRoom
    CopyCorner
    ReturnShelf
End Enum

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ComboBoxZone.DataSource = [Enum].GetValues(GetType(ServiceZone))
    End Sub

    Private Sub ButtonReadZone_Click(sender As Object, e As EventArgs) Handles ButtonReadZone.Click
        Dim zone As ServiceZone = CType(ComboBoxZone.SelectedItem, ServiceZone)
        LabelZone.Text = "目前服務區:" & BuildZoneText(zone)
    End Sub

    Private Function BuildZoneText(ByVal zone As ServiceZone) As String
        Select Case zone
            Case ServiceZone.FrontDesk
                Return "櫃台報到區"
            Case ServiceZone.ReadingRoom
                Return "安靜閱讀區"
            Case ServiceZone.CopyCorner
                Return "影印協助區"
            Case ServiceZone.ReturnShelf
                Return "歸還書架區"
            Case Else
                Return "未設定"
        End Select
    End Function
End Class
畫面輸出結果(選擇 ReadingRoom)
目前服務區:安靜閱讀區
邏輯解析
  • [Enum].GetValues 可取得列舉的所有值。
  • ComboBoxZone.SelectedItem 可轉回 ServiceZone
  • 畫面顯示文字可透過方法轉成中文,不一定直接顯示列舉名稱。

場景五:輸入文字安全轉成列舉

當列舉值來自文字輸入、設定檔或外部資料時,不建議直接使用 Parse。使用 Enum.TryParse 可以避免格式錯誤時直接拋出例外。

需要的主控項
  • TextBoxWindow:輸入取件時段名稱,例如 Morning
  • ButtonParseWindow:轉換文字。
  • LabelParseResult:顯示轉換結果。
範例程式碼
VB.NET / Windows Forms
Public Class Form1
    Private Sub ButtonParseWindow_Click(sender As Object, e As EventArgs) Handles ButtonParseWindow.Click
        Dim parsedWindow As PickupWindow
        Dim inputText As String = TextBoxWindow.Text.Trim()

        If [Enum].TryParse(Of PickupWindow)(inputText, True, parsedWindow) Then
            LabelParseResult.Text = "轉換成功:" & parsedWindow.ToString()
        Else
            LabelParseResult.Text = "轉換失敗:找不到此取件時段"
        End If
    End Sub
End Class
畫面輸出結果(TextBoxWindow = evening)
轉換成功:Evening
邏輯解析
  • TryParse 轉換成功會回傳 True
  • 第二個參數設為 True,代表不區分大小寫。
  • 外部文字來源不穩定時,TryParseParse 更適合。

Flags 列舉:可組合的選項

<Flags> 列舉:適合表示可以同時存在的多個選項,例如通知管道、功能開關、權限組合。每個成員值通常使用 1、2、4、8 這類二的冪次方。

場景六:活動提醒管道

單選狀態不需要 <Flags>,但若同一筆設定可以同時選擇多個項目,就適合使用。這個範例用活動提醒管道示範組合與檢查。

需要的主控項
  • CheckBoxScreen:螢幕顯示。
  • CheckBoxSms:簡訊提醒。
  • CheckBoxEmail:Email 提醒。
  • ButtonCheckChannel:檢查提醒管道。
  • LabelChannel:顯示結果。
範例程式碼
VB.NET / Windows Forms
<Flags>
Public Enum ReminderChannel
    None = 0
    Screen = 1
    Sms = 2
    Email = 4
End Enum

Public Class Form1
    Private Sub ButtonCheckChannel_Click(sender As Object, e As EventArgs) Handles ButtonCheckChannel.Click
        Dim channels As ReminderChannel = ReminderChannel.None

        If CheckBoxScreen.Checked Then
            channels = channels Or ReminderChannel.Screen
        End If

        If CheckBoxSms.Checked Then
            channels = channels Or ReminderChannel.Sms
        End If

        If CheckBoxEmail.Checked Then
            channels = channels Or ReminderChannel.Email
        End If

        If (channels And ReminderChannel.Sms) = ReminderChannel.Sms Then
            LabelChannel.Text = "已包含簡訊提醒|目前設定:" & channels.ToString()
        Else
            LabelChannel.Text = "未包含簡訊提醒|目前設定:" & channels.ToString()
        End If
    End Sub
End Class
畫面輸出結果(勾選 Screen 與 Sms)
已包含簡訊提醒|目前設定:Screen, Sms
邏輯解析
  • Or 用來把多個旗標組合在一起。
  • And 用來檢查目前組合是否包含指定旗標。
  • None = 0 代表沒有任何選項。
  • Flags 成員值使用 1、2、4,才能讓位元組合彼此不重疊。

使用限制:若某個欄位一次只能有一種狀態,例如「空位、已預約、使用中」三選一,就不需要使用 <Flags>。Flags 適合多選組合,不適合單選流程狀態。

實務判斷與常見誤區

常見問題整理

  • 把會變動的資料做成 Enum:例如商品類別、部門清單、客戶類型若會由資料庫維護,就不適合硬寫成列舉。
  • 只依賴預設數值:若列舉值要存到資料庫或對接外部系統,應明確指定數值。
  • 把顯示文字直接等同列舉名稱:列舉名稱適合程式閱讀,畫面文字可另外轉換。
  • Flags 值沒有使用 1、2、4、8:可能造成組合判斷錯誤。
  • 濫用 Flags:單選狀態不需要 Flags。
需求 建議做法 原因
固定單選狀態 一般 Enum 清楚、安全、適合流程判斷。
可同時選多項 <Flags> Enum。 可用位元運算組合多個選項。
資料庫會維護選項 資料表或設定檔。 不需要每次新增選項都改程式。
畫面要顯示中文 建立轉換方法。 避免把顯示文字與程式名稱綁死。

重點整理

  1. Enum 適合表示固定且具意義的選項集合。
  2. 未指定數值時,列舉成員預設從 0 開始遞增。
  3. 需要對應資料庫或外部代碼時,建議明確指定數值。
  4. Select Case 搭配列舉可讓流程判斷更清楚。
  5. [Enum].GetValues 可取得列舉所有值,適合搭配 ComboBox
  6. 外部文字轉列舉時,TryParse 比直接 Parse 更安全。
  7. <Flags> 適合可多選組合的情境,成員值應使用 1、2、4、8。
  8. 若選項會頻繁變動或由資料庫維護,就不適合硬寫成列舉。