VB.NET 屬性(Property) 筆記(進階篇)
Property 是類別對外提供的資料入口。外部看起來像是在讀寫欄位,但類別內部可以在讀取或寫入時加入驗證、格式整理、計算結果、變更通知與存取權限控制。
屬性的重點不是把欄位換一種寫法,而是把資料規則放回類別內部。若資料可以被外部任意改寫,物件狀態很容易失控;若使用屬性,就能讓資料在進入物件前先被檢查與整理。
先理解 Property 在做什麼
Property:提供受控制的讀取與寫入介面。Get 負責回傳資料,Set 負責接收外部指定的值,必要時可進行驗證、修正或通知。
屬性的核心價值
- 封裝欄位:外部不直接接觸內部儲存欄位。
- 保護資料:在
Set中檢查空白、範圍、格式。 - 提供推導值:在
Get中由其他資料計算結果。 - 控制寫入權限:可做成唯讀或
Private Set。 - 回報變更:可搭配
INotifyPropertyChanged通知畫面或其他程式。
| 成員 | 用途 | 實務理解 |
|---|---|---|
| Get | 讀取屬性值。 | 回傳欄位值或計算結果。 |
| Set | 寫入屬性值。 | value 是外部指定的新值。 |
| 後端欄位 | 實際保存資料。 | 通常宣告成 Private。 |
| ReadOnly | 只允許讀取。 | 適合計算結果或不可外部修改的狀態。 |
基本結構
VB.NET
Private _displayName As String
Public Property DisplayName As String
Get
Return _displayName
End Get
Set(value As String)
_displayName = value.Trim()
End Set
End Property
自動實作屬性
場景一:保存閱讀座位資料
若資料只是單純保存,不需要驗證、不需要額外計算,也不需要在寫入時做特殊處理,就可以使用自動實作屬性。
需要的主控項
TextBoxSeatCode:輸入座位代碼。TextBoxAreaName:輸入區域名稱。ButtonShowSeat:顯示座位資料。LabelSeatResult:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Class ReadingSeat
Public Property SeatCode As String
Public Property AreaName As String
End Class
Public Class Form1
Private currentSeat As New ReadingSeat()
Private Sub ButtonShowSeat_Click(sender As Object, e As EventArgs) Handles ButtonShowSeat.Click
currentSeat.SeatCode = TextBoxSeatCode.Text.Trim()
currentSeat.AreaName = TextBoxAreaName.Text.Trim()
LabelSeatResult.Text = "座位:" & currentSeat.AreaName & " / " & currentSeat.SeatCode
End Sub
End Class
畫面輸出結果(AreaName = 靜音區,SeatCode = A-12)
座位:靜音區 / A-12邏輯解析
SeatCode與AreaName只是單純保存資料。- 自動實作屬性由編譯器建立背後儲存欄位。
- 若後續需要限制座位格式,就應改成完整屬性。
完整屬性:在 Set 中加入規則
場景二:限制活動報名人數
完整屬性適合在寫入時清理資料、限制範圍或拒絕不合法內容。這個範例把姓名空白修正為未填寫,並把報名人數限制在 1 到 6 人。
需要的主控項
TextBoxGuestName:輸入報名名稱。TextBoxPeopleCount:輸入報名人數。ButtonCheckSignup:建立報名資料。LabelSignupResult:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Class WorkshopSignup
Private _guestName As String = "未填寫"
Private _peopleCount As Integer = 1
Public Property GuestName As String
Get
Return _guestName
End Get
Set(value As String)
Dim cleanedName As String = value.Trim()
If cleanedName = String.Empty Then
_guestName = "未填寫"
Else
_guestName = cleanedName
End If
End Set
End Property
Public Property PeopleCount As Integer
Get
Return _peopleCount
End Get
Set(value As Integer)
If value < 1 Then
_peopleCount = 1
ElseIf value > 6 Then
_peopleCount = 6
Else
_peopleCount = value
End If
End Set
End Property
End Class
Public Class Form1
Private signup As New WorkshopSignup()
Private Sub ButtonCheckSignup_Click(sender As Object, e As EventArgs) Handles ButtonCheckSignup.Click
signup.GuestName = TextBoxGuestName.Text
Dim countValue As Integer
If Integer.TryParse(TextBoxPeopleCount.Text.Trim(), countValue) Then
signup.PeopleCount = countValue
Else
signup.PeopleCount = 1
End If
LabelSignupResult.Text = "報名名稱:" & signup.GuestName & vbCrLf &
"報名人數:" & signup.PeopleCount.ToString()
End Sub
End Class
畫面輸出結果(GuestName = 林小姐,PeopleCount = 9)
報名名稱:林小姐
報名人數:6邏輯解析
GuestName在Set中移除前後空白。PeopleCount在Set中限制人數上限。- 規則放在屬性內,外部指定資料時會自動套用同一套檢查。
唯讀屬性:回傳推導結果
場景三:列印工作費用計算
某些值不應由外部直接指定,而是由其他屬性推導而來。例如總費用應由頁數、單頁費用與裝訂費計算,不應讓外部任意寫入。
需要的主控項
TextBoxPageCount:輸入頁數。ButtonCalculatePrint:計算列印費。LabelPrintResult:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Class PrintJob
Public Property PageCount As Integer
Public Property PricePerPage As Decimal = 1.5D
Public Property BindingFee As Decimal = 12D
Public ReadOnly Property TotalAmount As Decimal
Get
Return PageCount * PricePerPage + BindingFee
End Get
End Property
End Class
Public Class Form1
Private job As New PrintJob()
Private Sub ButtonCalculatePrint_Click(sender As Object, e As EventArgs) Handles ButtonCalculatePrint.Click
Dim pages As Integer
If Not Integer.TryParse(TextBoxPageCount.Text.Trim(), pages) OrElse pages <= 0 Then
LabelPrintResult.Text = "請輸入正確頁數"
Return
End If
job.PageCount = pages
LabelPrintResult.Text = "頁數:" & job.PageCount.ToString() & vbCrLf &
"總費用:" & job.TotalAmount.ToString("N1") & " 元"
End Sub
End Class
畫面輸出結果(PageCount = 20)
頁數:20
總費用:42.0 元邏輯解析
TotalAmount沒有Set,外部不能直接指定。- 每次讀取
TotalAmount時,都會依目前屬性重新計算。 - 推導值使用唯讀屬性,可避免資料來源互相矛盾。
Private Set:外部可讀,內部可寫
場景四:取件單號與建立時間
系統產生的單號與建立時間通常可以被外部讀取,但不應被外部任意修改。這類資料適合使用 Private Set。
需要的主控項
ButtonCreatePickup:建立取件單。LabelPickupResult:顯示取件資料。
範例程式碼
VB.NET / Windows Forms
Public Class PickupTicket
Public Property TicketNo As String
Get
Return _ticketNo
End Get
Private Set(value As String)
_ticketNo = value
End Set
End Property
Public Property CreatedAt As DateTime
Get
Return _createdAt
End Get
Private Set(value As DateTime)
_createdAt = value
End Set
End Property
Private _ticketNo As String
Private _createdAt As DateTime
Public Sub New()
CreatedAt = DateTime.Now
TicketNo = "P" & CreatedAt.ToString("yyyyMMddHHmmss")
End Sub
End Class
Public Class Form1
Private currentTicket As PickupTicket
Private Sub ButtonCreatePickup_Click(sender As Object, e As EventArgs) Handles ButtonCreatePickup.Click
currentTicket = New PickupTicket()
LabelPickupResult.Text = "取件單號:" & currentTicket.TicketNo & vbCrLf &
"建立時間:" & currentTicket.CreatedAt.ToString("yyyy/MM/dd HH:mm:ss")
End Sub
End Class
畫面輸出結果
取件單號:依執行時間產生
建立時間:依執行當下時間顯示邏輯解析
TicketNo與CreatedAt對外可讀。Private Set代表只有類別內部能設定值。- 系統產生的資料使用
Private Set,可避免外部誤改。
INotifyPropertyChanged:屬性變更通知
場景五:點餐項目變更後更新摘要
當屬性改變時,畫面或其他程式可能需要同步更新。INotifyPropertyChanged 提供標準事件,讓屬性變更可以被外部接收到。
需要的主控項
TextBoxItemName:輸入品項名稱。TextBoxQuantity:輸入數量。TextBoxUnitPrice:輸入單價。ButtonUpdateOrder:更新訂單。LabelOrderSummary:顯示摘要。LabelOrderTotal:顯示總金額。
範例程式碼
VB.NET / Windows Forms
Imports System.ComponentModel
Public Class OrderLineViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _itemName As String = "未選擇"
Private _quantity As Integer = 1
Private _unitPrice As Decimal = 0D
Public Property ItemName As String
Get
Return _itemName
End Get
Set(value As String)
Dim newName As String = value.Trim()
If newName = String.Empty Then newName = "未選擇"
If _itemName <> newName Then
_itemName = newName
RaiseChanged(NameOf(ItemName))
RaiseChanged(NameOf(SummaryText))
End If
End Set
End Property
Public Property Quantity As Integer
Get
Return _quantity
End Get
Set(value As Integer)
Dim newQuantity As Integer = Math.Max(1, value)
If _quantity <> newQuantity Then
_quantity = newQuantity
RaiseChanged(NameOf(Quantity))
RaiseChanged(NameOf(TotalAmount))
RaiseChanged(NameOf(SummaryText))
End If
End Set
End Property
Public Property UnitPrice As Decimal
Get
Return _unitPrice
End Get
Set(value As Decimal)
Dim newPrice As Decimal = Math.Max(0D, value)
If _unitPrice <> newPrice Then
_unitPrice = newPrice
RaiseChanged(NameOf(UnitPrice))
RaiseChanged(NameOf(TotalAmount))
RaiseChanged(NameOf(SummaryText))
End If
End Set
End Property
Public ReadOnly Property TotalAmount As Decimal
Get
Return Quantity * UnitPrice
End Get
End Property
Public ReadOnly Property SummaryText As String
Get
Return ItemName & " x " & Quantity.ToString()
End Get
End Property
Private Sub RaiseChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Public Class Form1
Private line As New OrderLineViewModel()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler line.PropertyChanged, AddressOf LineChanged
End Sub
Private Sub ButtonUpdateOrder_Click(sender As Object, e As EventArgs) Handles ButtonUpdateOrder.Click
line.ItemName = TextBoxItemName.Text
Dim quantityValue As Integer
If Integer.TryParse(TextBoxQuantity.Text.Trim(), quantityValue) Then
line.Quantity = quantityValue
End If
Dim priceValue As Decimal
If Decimal.TryParse(TextBoxUnitPrice.Text.Trim(), priceValue) Then
line.UnitPrice = priceValue
End If
End Sub
Private Sub LineChanged(sender As Object, e As PropertyChangedEventArgs)
LabelOrderSummary.Text = line.SummaryText
LabelOrderTotal.Text = "總金額:" & line.TotalAmount.ToString("N0") & " 元"
End Sub
End Class
畫面輸出結果(ItemName = 咖啡豆,Quantity = 3,UnitPrice = 280)
咖啡豆 x 3
總金額:840 元邏輯解析
INotifyPropertyChanged讓屬性變更可被外部知道。- 當
Quantity或UnitPrice改變時,也要通知TotalAmount改變。 - 推導屬性本身沒有儲存值,但依賴的資料改變時,仍應發出通知。
預設索引屬性
場景六:用置物櫃編號讀寫備註
若物件本身像一個集合,可以使用 Default Public Property 建立預設索引屬性。呼叫時可寫成 lockerNotes("B014")。
需要的主控項
TextBoxLockerNo:輸入置物櫃編號。TextBoxLockerNote:輸入備註。ButtonSaveNote:儲存備註。LabelNoteResult:顯示結果。
範例程式碼
VB.NET / Windows Forms
Public Class LockerNoteBook
Private notes As New Dictionary(Of String, String)()
Default Public Property Item(ByVal lockerNo As String) As String
Get
Dim key As String = NormalizeKey(lockerNo)
If notes.ContainsKey(key) Then
Return notes(key)
End If
Return "尚無備註"
End Get
Set(value As String)
Dim key As String = NormalizeKey(lockerNo)
Dim noteText As String = value.Trim()
If noteText = String.Empty Then
notes.Remove(key)
Else
notes(key) = noteText
End If
End Set
End Property
Private Function NormalizeKey(ByVal lockerNo As String) As String
Return lockerNo.Trim().ToUpper()
End Function
End Class
Public Class Form1
Private lockerNotes As New LockerNoteBook()
Private Sub ButtonSaveNote_Click(sender As Object, e As EventArgs) Handles ButtonSaveNote.Click
Dim lockerNo As String = TextBoxLockerNo.Text.Trim()
If lockerNo = String.Empty Then
LabelNoteResult.Text = "請輸入置物櫃編號"
Return
End If
lockerNotes(lockerNo) = TextBoxLockerNote.Text
LabelNoteResult.Text = lockerNo.ToUpper() & ":" & lockerNotes(lockerNo)
End Sub
End Class
畫面輸出結果(LockerNo = b014,Note = 需清潔)
B014:需清潔邏輯解析
Default Public Property Item(...)讓物件可以用索引方式存取。Get找不到備註時回傳預設文字。Set收到空白備註時移除該筆資料。
屬性與方法的差異
| 比較項目 | Property | Method |
|---|---|---|
| 語意 | 描述資料、狀態、特徵。 | 描述動作、流程、行為。 |
| 呼叫感受 | 像讀取或設定資料。 | 像執行一件事情。 |
| 適合內容 | 輕量驗證、格式整理、推導結果。 | 儲存檔案、查詢資料庫、寄信、長時間計算。 |
| 常見名稱 | TotalAmount、IsActive。 |
Save()、LoadData()、SendMail()。 |
屬性不適合放重型流程
- 不要在屬性中做檔案 I/O:讀取屬性不應造成長時間等待。
- 不要在屬性中呼叫網路服務:屬性看起來像資料,不應暗藏昂貴操作。
- 不要在 Get 中改變主要狀態:讀取屬性應盡量避免造成副作用。
- 動作語意應使用方法:例如儲存、寄送、重新載入,應寫成
Sub或Function。
實務判斷與常見誤區
| 需求 | 建議寫法 | 原因 |
|---|---|---|
| 單純保存資料 | 自動實作屬性。 | 程式簡短,語意清楚。 |
| 寫入時要檢查 | 完整屬性。 | 可在 Set 中集中驗證。 |
| 由其他值計算 | 唯讀屬性。 | 避免外部指定矛盾結果。 |
| 外部可讀不可寫 | Private Set。 |
適合系統產生的單號與時間。 |
| 屬性變更要通知畫面 | INotifyPropertyChanged。 |
可集中處理資料變更事件。 |
封裝原則:欄位通常設為 Private,外部透過屬性存取。這樣資料規則會集中在類別中,不會散落在多個按鈕事件或表單流程裡。
重點整理
Property是類別對外提供的受控制資料入口。- 自動實作屬性適合單純保存資料。
- 完整屬性適合在
Set中加入驗證、清理與限制。 - 唯讀屬性適合回傳由其他資料推導出的結果。
Private Set適合外部可讀、內部才可改的資料。INotifyPropertyChanged可在屬性變更時發出通知。- 預設索引屬性適合讓物件像集合一樣用索引存取。
- 屬性應保持輕量,重型流程與明確動作應使用方法。