VB.NET 結構(Structure) 筆記(進階篇)
Structure 是 VB.NET 中用來表示一組小型相關資料的值型別。它可以包含欄位、屬性、建構函式、方法、事件與介面實作,常用來描述座標、尺寸、範圍、金額組合、日期區間與簡單狀態。
Structure 的重點不是「比 Class 快」這麼單一,而是它具有值語意。將結構指定給另一個變數時,通常會複製整份值;修改副本不會改到原本資料。若資料很大、需要身份識別、需要共享狀態或需要繼承,通常應優先使用 Class。
先理解 Structure 是什麼
Structure:一種值型別,用來把少量相關資料包成一個整體。它沒有物件身份的概念,重點在「值本身是否相同」,而不是「是否指向同一個物件」。
Structure 的核心特性
- 值型別:指定或傳遞時,通常會複製整份值。
- 適合小型資料:例如座標、尺寸、日期範圍、色彩分量。
- 可包含成員:可宣告屬性、方法、建構函式與介面實作。
- 不可繼承:Structure 不能繼承其他 Class 或 Structure,也不能被繼承。
- 有預設值:未指定時,各欄位會是該型別的預設值。
記憶體提醒:Structure 是值型別,但不代表永遠直接放在堆疊。實際位置會受欄位、陣列、集合、裝箱等情境影響。實務判斷應以「值語意、複製成本、資料大小」為主,不要只用堆疊或堆積來判斷。
| 項目 | Structure | 實務理解 |
|---|---|---|
| 型別特性 | 值型別。 | 指定與傳遞時要注意複製。 |
| 資料意義 | 表示一組值。 | 適合座標、尺寸、範圍。 |
| 是否可繼承 | 不可繼承,也不可被繼承。 | 可實作介面,但不做繼承層級。 |
| 是否適合大量狀態 | 不適合。 | 大型或複雜狀態通常使用 Class。 |
定義 Structure 與建構函式
基本語法
Public Structure StructureName
Public Property ValueA As Integer
Public Property ValueB As Integer
Public Sub New(valueA As Integer, valueB As Integer)
Me.ValueA = valueA
Me.ValueB = valueB
End Sub
End Structure
Structure 可以定義帶參數的建構函式。建構函式結束前,所有欄位或自動屬性都應完成初始化。
場景一:自習座位位置
座位位置由區域、列號與座位號組成,本身只是小型資料,不需要物件身份,也不需要繼承,因此適合設計成 Structure。
需要的主控項
TextBoxArea:輸入區域代碼。TextBoxRow:輸入列號。TextBoxSeatNo:輸入座位號。ButtonBuildSeat:建立座位位置。LabelSeat:顯示結果。
範例程式碼
Public Structure SeatPosition
Public ReadOnly Property AreaCode As String
Public ReadOnly Property RowNo As Integer
Public ReadOnly Property SeatNo As Integer
Public Sub New(areaCode As String, rowNo As Integer, seatNo As Integer)
Me.AreaCode = areaCode.Trim().ToUpper()
Me.RowNo = rowNo
Me.SeatNo = seatNo
End Sub
Public Overrides Function ToString() As String
Return AreaCode & "區-第" & RowNo.ToString() & "列-" & SeatNo.ToString("00") & "號"
End Function
End Structure
Public Class Form1
Private Sub ButtonBuildSeat_Click(sender As Object, e As EventArgs) Handles ButtonBuildSeat.Click
Dim rowNo As Integer
Dim seatNo As Integer
If String.IsNullOrWhiteSpace(TextBoxArea.Text) Then
LabelSeat.Text = "請輸入區域代碼"
Return
End If
If Not Integer.TryParse(TextBoxRow.Text.Trim(), rowNo) OrElse rowNo <= 0 Then
LabelSeat.Text = "列號必須大於 0"
Return
End If
If Not Integer.TryParse(TextBoxSeatNo.Text.Trim(), seatNo) OrElse seatNo <= 0 Then
LabelSeat.Text = "座位號必須大於 0"
Return
End If
Dim seat As New SeatPosition(TextBoxArea.Text, rowNo, seatNo)
LabelSeat.Text = "座位位置:" & seat.ToString()
End Sub
End Class
邏輯解析
SeatPosition封裝區域、列號與座位號。- 屬性使用
ReadOnly,建立後不從外部任意修改。 ToString負責把值整理成容易顯示的格式。
值型別複製行為
複製語意:Structure 指定給另一個變數時,通常會複製整份值。之後修改副本,不會影響原本的 Structure 變數。
場景二:包材尺寸複製後微調
這個範例先建立原始包材尺寸,再複製成另一個變數進行微調。修改副本寬度後,原始尺寸仍保持原值。
需要的主控項
ButtonCopySize:執行尺寸複製。LabelCopyResult:顯示結果。
範例程式碼
Public Structure PackageSize
Public Property Width As Integer
Public Property Height As Integer
Public Sub New(width As Integer, height As Integer)
Me.Width = width
Me.Height = height
End Sub
Public Overrides Function ToString() As String
Return Width.ToString() & " x " & Height.ToString()
End Function
End Structure
Public Class Form1
Private Sub ButtonCopySize_Click(sender As Object, e As EventArgs) Handles ButtonCopySize.Click
Dim originalSize As New PackageSize(30, 20)
Dim adjustedSize As PackageSize = originalSize
adjustedSize.Width = 36
LabelCopyResult.Text = "原始尺寸:" & originalSize.ToString() & vbCrLf &
"調整尺寸:" & adjustedSize.ToString()
End Sub
End Class
邏輯解析
adjustedSize = originalSize會複製 Structure 的值。- 修改
adjustedSize.Width不會改到originalSize.Width。 - 若 Structure 內含參考型別欄位,複製的是參考值;參考內部物件仍需另外小心。
屬性、方法與推導結果
場景三:寄物櫃尺寸判斷
Structure 可以把資料與簡單計算放在一起。寄物櫃尺寸由寬、高、深組成,體積與是否超大件都可以由這三個值推導出來。
需要的主控項
TextBoxWidth:輸入寬度。TextBoxHeight:輸入高度。TextBoxDepth:輸入深度。ButtonCheckLockerSize:檢查尺寸。LabelLockerSize:顯示結果。
範例程式碼
Public Structure LockerSize
Public ReadOnly Property Width As Integer
Public ReadOnly Property Height As Integer
Public ReadOnly Property Depth As Integer
Public Sub New(width As Integer, height As Integer, depth As Integer)
Me.Width = width
Me.Height = height
Me.Depth = depth
End Sub
Public ReadOnly Property Volume As Integer
Get
Return Width * Height * Depth
End Get
End Property
Public Function IsOversize() As Boolean
Return Volume > 60000
End Function
End Structure
Public Class Form1
Private Sub ButtonCheckLockerSize_Click(sender As Object, e As EventArgs) Handles ButtonCheckLockerSize.Click
Dim width As Integer
Dim height As Integer
Dim depth As Integer
If Not Integer.TryParse(TextBoxWidth.Text.Trim(), width) OrElse width <= 0 Then
LabelLockerSize.Text = "寬度格式錯誤"
Return
End If
If Not Integer.TryParse(TextBoxHeight.Text.Trim(), height) OrElse height <= 0 Then
LabelLockerSize.Text = "高度格式錯誤"
Return
End If
If Not Integer.TryParse(TextBoxDepth.Text.Trim(), depth) OrElse depth <= 0 Then
LabelLockerSize.Text = "深度格式錯誤"
Return
End If
Dim size As New LockerSize(width, height, depth)
LabelLockerSize.Text = "體積:" & size.Volume.ToString("N0") & vbCrLf &
"尺寸判斷:" & If(size.IsOversize(), "超大件", "一般件")
End Sub
End Class
邏輯解析
Volume是由寬、高、深推導出的唯讀屬性。IsOversize是簡單判斷方法,依目前尺寸回傳 Boolean。- Structure 適合放輕量計算,不適合放資料庫查詢或長時間流程。
Structure 實作介面
場景四:展示牌面積計算
Structure 不能繼承其他類別,但可以實作介面。若多種形狀都需要提供面積,就可以定義共同介面,再由不同 Structure 實作。
需要的主控項
TextBoxBoardWidth:輸入展示牌寬度。TextBoxBoardHeight:輸入展示牌高度。ButtonShowBoardArea:計算面積。LabelBoardArea:顯示結果。
範例程式碼
Public Interface IAreaSource
Function GetArea() As Decimal
End Interface
Public Structure DisplayBoard
Implements IAreaSource
Public ReadOnly Property Width As Decimal
Public ReadOnly Property Height As Decimal
Public Sub New(width As Decimal, height As Decimal)
Me.Width = width
Me.Height = height
End Sub
Public Function GetArea() As Decimal Implements IAreaSource.GetArea
Return Width * Height
End Function
End Structure
Public Class Form1
Private Sub ButtonShowBoardArea_Click(sender As Object, e As EventArgs) Handles ButtonShowBoardArea.Click
Dim width As Decimal
Dim height As Decimal
If Not Decimal.TryParse(TextBoxBoardWidth.Text.Trim(), width) OrElse width <= 0D Then
LabelBoardArea.Text = "寬度格式錯誤"
Return
End If
If Not Decimal.TryParse(TextBoxBoardHeight.Text.Trim(), height) OrElse height <= 0D Then
LabelBoardArea.Text = "高度格式錯誤"
Return
End If
Dim board As New DisplayBoard(width, height)
Dim areaSource As IAreaSource = board
LabelBoardArea.Text = "展示牌面積:" & areaSource.GetArea().ToString("N2")
End Sub
End Class
邏輯解析
DisplayBoard使用Implements IAreaSource實作介面。- 介面讓不同型別可以提供相同方法名稱。
- Structure 指派給介面變數時可能產生裝箱,頻繁大量使用時要注意成本。
相等性比較
場景五:座位代碼相等比較
Structure 通常表示值,因此相等性常以欄位內容判斷。若需要使用 = 與 <> 比較兩個結構,可自行定義運算子。
需要的主控項
ButtonCompareSeat:比較座位。LabelCompareSeat:顯示結果。
範例程式碼
Public Structure SeatKey
Public ReadOnly Property AreaCode As String
Public ReadOnly Property Number As Integer
Public Sub New(areaCode As String, number As Integer)
Me.AreaCode = areaCode.Trim().ToUpper()
Me.Number = number
End Sub
Public Overrides Function Equals(obj As Object) As Boolean
If TypeOf obj Is SeatKey Then
Dim other As SeatKey = DirectCast(obj, SeatKey)
Return AreaCode = other.AreaCode AndAlso Number = other.Number
End If
Return False
End Function
Public Overrides Function GetHashCode() As Integer
Return AreaCode.GetHashCode() Xor Number.GetHashCode()
End Function
Public Shared Operator =(left As SeatKey, right As SeatKey) As Boolean
Return left.Equals(right)
End Operator
Public Shared Operator <>(left As SeatKey, right As SeatKey) As Boolean
Return Not left.Equals(right)
End Operator
End Structure
Public Class Form1
Private Sub ButtonCompareSeat_Click(sender As Object, e As EventArgs) Handles ButtonCompareSeat.Click
Dim firstSeat As New SeatKey("a", 12)
Dim secondSeat As New SeatKey("A", 12)
If firstSeat = secondSeat Then
LabelCompareSeat.Text = "座位相同"
Else
LabelCompareSeat.Text = "座位不同"
End If
End Sub
End Class
邏輯解析
- 建構時把區域代碼統一轉成大寫。
Equals負責定義相等規則。- 覆寫
Equals時,也應一起覆寫GetHashCode。 =與<>運算子讓比較語法更直覺。
集合中的 Structure 更新
場景六:更新置物櫃狀態
Structure 放在集合中時,要注意取出的值通常是副本。安全做法是先取出、修改副本,再把修改後的值指定回集合。
需要的主控項
ButtonUpdateLocker:更新置物櫃。ListBoxLocker:顯示置物櫃清單。
範例程式碼
Public Structure LockerStatus
Public Property LockerNo As String
Public Property IsOccupied As Boolean
Public Sub New(lockerNo As String, isOccupied As Boolean)
Me.LockerNo = lockerNo
Me.IsOccupied = isOccupied
End Sub
Public Overrides Function ToString() As String
Return LockerNo & ":" & If(IsOccupied, "使用中", "空櫃")
End Function
End Structure
Public Class Form1
Private lockers As New List(Of LockerStatus) From {
New LockerStatus("A01", False),
New LockerStatus("A02", False),
New LockerStatus("A03", False)
}
Private Sub ButtonUpdateLocker_Click(sender As Object, e As EventArgs) Handles ButtonUpdateLocker.Click
Dim index As Integer = 1
Dim item As LockerStatus = lockers(index)
item.IsOccupied = True
lockers(index) = item
ListBoxLocker.Items.Clear()
For Each locker As LockerStatus In lockers
ListBoxLocker.Items.Add(locker.ToString())
Next
End Sub
End Class
邏輯解析
lockers(index)取出的 Structure 可視為一份值。- 修改
item後,需要再寫回lockers(index)。 - 若資料會頻繁被修改,或需要共享同一個狀態,使用 Class 通常更自然。
Structure 與 Class 的選擇
| 判斷項目 | 適合 Structure | 適合 Class |
|---|---|---|
| 資料大小 | 小型資料,欄位少。 | 大型資料或欄位很多。 |
| 資料語意 | 重點是值本身。 | 重點是物件身份。 |
| 修改頻率 | 建立後少修改,或設計為不可變。 | 狀態會頻繁改變。 |
| 共享需求 | 不需要多處共享同一個實例狀態。 | 多處要操作同一個物件。 |
| 繼承設計 | 不需要繼承。 | 需要繼承、多型或複雜行為。 |
場景七:會員資料應使用 Class
會員資料通常有身份、歷史紀錄、狀態變化與多個流程共同操作,不適合只當成一組值來複製。這類資料使用 Class 會更合理。
範例程式碼
Public Class MemberAccount
Public Property MemberNo As String
Public Property DisplayName As String
Public Property PointBalance As Integer
Public Sub AddPoints(points As Integer)
If points > 0 Then
PointBalance += points
End If
End Sub
End Class
邏輯解析
- 會員帳號有明確身份,不只是單純值。
- 點數餘額會持續改變,適合保留同一個物件狀態。
- 需要長期存在、共享與行為擴充時,Class 比 Structure 更合適。
實務判斷與常見誤區
常見問題整理
- 把 Structure 當小型 Class:若資料需要身份、共享狀態或繼承,應使用 Class。
- 設計過大的 Structure:欄位很多時,複製成本會變高。
- 可變 Structure 太複雜:容易在指定、傳參數或集合中產生副本混淆。
- 誤以為永遠在堆疊:值型別的重點是值語意,不是固定儲存位置。
- 頻繁裝箱:Structure 轉成
Object或介面時可能產生裝箱成本。 - 忘記預設值:Structure 即使未呼叫自訂建構函式,也會有預設值。
| 需求 | 建議 | 原因 |
|---|---|---|
| 座標、尺寸、範圍 | 可考慮 Structure。 | 小型值資料,語意自然。 |
| 建立後不應修改 | 使用唯讀屬性。 | 降低副本修改造成的混淆。 |
| 需要可空狀態 | 使用 Nullable(Of T) 或 T? 語法。 |
一般 Structure 變數本身有預設值。 |
| 需要身份與共享狀態 | 使用 Class。 | 參考型別更符合物件身份語意。 |
| 需要繼承與多型 | 使用 Class 或介面設計。 | Structure 不支援繼承。 |
設計建議:Structure 最適合表示小型、完整、彼此相關、可以複製的值。若一份資料被修改後,其他地方也應看到同一份變化,通常代表它比較像物件,應使用 Class。
重點整理
Structure是值型別,適合表示小型相關資料。- Structure 的重點是值語意,指定或傳遞時要注意複製。
- Structure 可以包含屬性、方法、建構函式與介面實作。
- Structure 不支援繼承,也不能被其他型別繼承。
- Structure 建立後若不需要修改,建議使用唯讀屬性降低混淆。
- 覆寫相等性時,應同時處理
Equals與GetHashCode。 - Structure 放在集合中更新時,常見安全做法是取出、修改、再寫回。
- 需要身份、共享狀態、繼承或複雜行為時,應使用
Class。