VB.NET AddressOf 筆記(進階篇)
AddressOf 用來取得既有方法,並把方法轉成符合指定委派型別的值。它不是直接執行方法,而是把「之後可以呼叫的方法」交給委派、事件、執行緒或其他需要回呼的 API。
常見用途包括事件處理、動態切換處理流程、把工作方法交給 Thread、將多個通知方法串在一起,以及把既有方法指定給 Action 或 Func。理解 AddressOf 的關鍵,是先理解委派:委派就是一種「方法格式」。方法參數與回傳值符合委派格式,才能被 AddressOf 指派。
先理解 AddressOf 在做什麼
AddressOf:取得方法並建立委派實例。語法上要寫方法名稱,不加括號,不傳入參數。例如 AddressOf BuildCode 是把方法交出去;BuildCode() 則是立刻執行方法。
AddressOf 的核心觀念
- 不是呼叫方法:
AddressOf MethodName只是建立可呼叫的委派。 - 需要目標型別:必須知道要轉成哪一種委派。
- 簽章要相容:參數與回傳值應符合委派格式。
- 適合既有方法:方法已經寫好,想交給事件、委派或執行緒使用。
- 不是萬用替代品:需要臨時包裝參數時,Lambda 通常更自然。
基本語法
Public Delegate Function TextBuilder(source As String) As String
Private currentBuilder As TextBuilder = AddressOf BuildSimpleText
Private Function BuildSimpleText(source As String) As String
Return source.Trim()
End Function
TextBuilder 是委派型別,代表「接受一個字串,回傳一個字串」的方法格式。BuildSimpleText 符合這個格式,因此可以用 AddressOf 指派給 currentBuilder。
| 寫法 | 意思 | 結果 |
|---|---|---|
AddressOf BuildText |
取得方法並轉成委派。 | 方法尚未執行。 |
BuildText("A") |
直接呼叫方法。 | 方法立即執行並回傳結果。 |
AddHandler ... AddressOf ... |
把方法掛到事件。 | 事件發生時才執行。 |
New Thread(AddressOf ...) |
把方法交給執行緒啟動。 | Start 後由新執行緒執行。 |
委派變數:動態切換要執行的方法
場景一:貨架標籤格式切換
同一筆貨架代碼可能需要輸出成不同格式。這個範例用委派變數保存目前選擇的格式方法,按下按鈕時只呼叫目前委派,不需要在計算按鈕中重複寫多段判斷。
需要的主控項
TextBoxShelfCode:輸入貨架代碼。ComboBoxFormat:選擇格式。ButtonBuildLabel:產生標籤。LabelShelfResult:顯示結果。
範例程式碼
Public Class Form1
Public Delegate Function ShelfFormatter(source As String) As String
Private currentFormatter As ShelfFormatter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ComboBoxFormat.Items.Clear()
ComboBoxFormat.Items.Add("一般標籤")
ComboBoxFormat.Items.Add("盤點標籤")
ComboBoxFormat.Items.Add("出貨標籤")
ComboBoxFormat.SelectedIndex = 0
End Sub
Private Sub ComboBoxFormat_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBoxFormat.SelectedIndexChanged
Select Case ComboBoxFormat.SelectedIndex
Case 0
currentFormatter = AddressOf BuildNormalLabel
Case 1
currentFormatter = AddressOf BuildCheckLabel
Case 2
currentFormatter = AddressOf BuildShippingLabel
End Select
End Sub
Private Sub ButtonBuildLabel_Click(sender As Object, e As EventArgs) Handles ButtonBuildLabel.Click
Dim sourceCode As String = TextBoxShelfCode.Text.Trim()
If sourceCode = String.Empty Then
LabelShelfResult.Text = "請輸入貨架代碼"
Return
End If
If currentFormatter Is Nothing Then
LabelShelfResult.Text = "尚未選擇格式"
Return
End If
LabelShelfResult.Text = currentFormatter(sourceCode)
End Sub
Private Function BuildNormalLabel(source As String) As String
Return "貨架:" & source.ToUpper()
End Function
Private Function BuildCheckLabel(source As String) As String
Return "盤點-" & source.ToUpper() & "-請確認數量"
End Function
Private Function BuildShippingLabel(source As String) As String
Return "SHIP/" & source.ToUpper()
End Function
End Class
邏輯解析
ShelfFormatter規定方法必須接收字串並回傳字串。AddressOf BuildShippingLabel把既有方法指定給委派變數。currentFormatter(sourceCode)才是真正呼叫目前方法。
事件綁定:AddHandler 與 RemoveHandler
AddHandler:在執行期間把事件和方法連起來。Handles 適合固定綁定;AddHandler 適合動態綁定、批次綁定或依條件綁定。
場景二:多個快捷按鈕共用同一個事件方法
多個按鈕執行相似邏輯時,可以在表單載入時用 AddHandler 批次綁定同一個方法,再從 sender 判斷是哪個按鈕觸發。
需要的主控項
ButtonMorning:早班。ButtonAfternoon:午班。ButtonNight:晚班。LabelShiftResult:顯示選擇結果。
範例程式碼
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler ButtonMorning.Click, AddressOf SelectShift
AddHandler ButtonAfternoon.Click, AddressOf SelectShift
AddHandler ButtonNight.Click, AddressOf SelectShift
End Sub
Private Sub SelectShift(sender As Object, e As EventArgs)
Dim clickedButton As Button = CType(sender, Button)
LabelShiftResult.Text = "已選擇班別:" & clickedButton.Text
End Sub
End Class
邏輯解析
Button.Click需要符合Sub(sender As Object, e As EventArgs)格式的方法。AddressOf SelectShift把方法綁到三個按鈕的 Click 事件。sender代表觸發事件的來源控制項。
場景三:暫停與恢復即時檢查
動態事件綁定也需要能移除。若同一個事件被重複 AddHandler,同一個方法可能被呼叫多次,因此通常會搭配旗標管理綁定狀態。
需要的主控項
TextBoxScanCode:輸入掃描碼。ButtonEnableCheck:啟用檢查。ButtonDisableCheck:停用檢查。LabelScanResult:顯示檢查結果。
範例程式碼
Public Class Form1
Private checkRegistered As Boolean = False
Private Sub ButtonEnableCheck_Click(sender As Object, e As EventArgs) Handles ButtonEnableCheck.Click
If checkRegistered Then
LabelScanResult.Text = "即時檢查已啟用"
Return
End If
AddHandler TextBoxScanCode.TextChanged, AddressOf ScanCodeChanged
checkRegistered = True
LabelScanResult.Text = "即時檢查已啟用"
End Sub
Private Sub ButtonDisableCheck_Click(sender As Object, e As EventArgs) Handles ButtonDisableCheck.Click
If Not checkRegistered Then
LabelScanResult.Text = "即時檢查尚未啟用"
Return
End If
RemoveHandler TextBoxScanCode.TextChanged, AddressOf ScanCodeChanged
checkRegistered = False
LabelScanResult.Text = "即時檢查已停用"
End Sub
Private Sub ScanCodeChanged(sender As Object, e As EventArgs)
Dim codeText As String = TextBoxScanCode.Text.Trim().ToUpper()
If codeText.StartsWith("A-") Then
LabelScanResult.Text = "格式:A 類掃描碼"
Else
LabelScanResult.Text = "格式:待確認"
End If
End Sub
End Class
邏輯解析
AddHandler使用AddressOf加入事件處理方法。RemoveHandler使用同一個方法移除事件處理方法。checkRegistered避免同一個處理器被重複加入。
ThreadStart:把方法交給執行緒
場景四:背景檢查完成後通知畫面
Thread 建構函式需要一個可執行的方法入口。AddressOf RunTicketCheck 會建立符合 ThreadStart 的委派;真正開始執行的時間點是呼叫 Start。
需要的主控項
ButtonStartTicketCheck:開始背景檢查。LabelThreadResult:顯示狀態。
範例程式碼
Imports System.Threading
Public Class Form1
Private Sub ButtonStartTicketCheck_Click(sender As Object, e As EventArgs) Handles ButtonStartTicketCheck.Click
LabelThreadResult.Text = "票券檢查中..."
Dim worker As New Thread(AddressOf RunTicketCheck)
worker.IsBackground = True
worker.Start()
End Sub
Private Sub RunTicketCheck()
Thread.Sleep(800)
If LabelThreadResult.IsHandleCreated AndAlso Not LabelThreadResult.IsDisposed Then
LabelThreadResult.BeginInvoke(New MethodInvoker(AddressOf ShowTicketFinished))
End If
End Sub
Private Sub ShowTicketFinished()
LabelThreadResult.Text = "票券檢查完成"
End Sub
End Class
邏輯解析
New Thread(AddressOf RunTicketCheck)只是指定背景方法。worker.Start()之後,背景執行緒才開始執行。- 背景執行緒不能直接更新 Windows Forms 控制項,因此用
BeginInvoke回到 UI 執行緒。
多重委派:一次通知多個方法
場景五:入庫完成後同時更新紀錄
多重委派可以把多個 Sub 串在同一個委派中。呼叫委派時,會依序執行委派清單中的方法。這類寫法適合通知、紀錄與狀態更新。
需要的主控項
TextBoxReceiveCode:輸入入庫代碼。ButtonReceiveItem:完成入庫。ListBoxReceiveLog:顯示紀錄。LabelReceiveStatus:顯示狀態。
範例程式碼
Public Class Form1
Public Delegate Sub ReceiveNotice(message As String)
Private receiveHandlers As ReceiveNotice
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
receiveHandlers = New ReceiveNotice(AddressOf AddReceiveLog)
receiveHandlers = CType([Delegate].Combine(receiveHandlers,
New ReceiveNotice(AddressOf ShowReceiveStatus)),
ReceiveNotice)
End Sub
Private Sub ButtonReceiveItem_Click(sender As Object, e As EventArgs) Handles ButtonReceiveItem.Click
Dim receiveCode As String = TextBoxReceiveCode.Text.Trim().ToUpper()
If receiveCode = String.Empty Then
LabelReceiveStatus.Text = "請輸入入庫代碼"
Return
End If
receiveHandlers.Invoke("入庫完成:" & receiveCode)
End Sub
Private Sub AddReceiveLog(message As String)
ListBoxReceiveLog.Items.Add(DateTime.Now.ToString("HH:mm:ss") & " " & message)
End Sub
Private Sub ShowReceiveStatus(message As String)
LabelReceiveStatus.Text = message
End Sub
End Class
邏輯解析
ReceiveNotice規定通知方法必須接收一個字串且不回傳值。Delegate.Combine把多個通知方法串成同一個委派。- 多重委派若有回傳值,通常只會取得最後一個方法的回傳結果,因此通知型
Sub更直覺。
Action、Func 與 Lambda 的選擇
| 寫法 | 適合情境 | 注意事項 |
|---|---|---|
| 自訂 Delegate + AddressOf | 需要有明確業務名稱的方法格式。 | 較清楚,但要先宣告委派型別。 |
| Action + AddressOf | 沒有回傳值的方法。 | 名稱較通用,語意可能不如自訂委派。 |
| Func + AddressOf | 有回傳值的方法。 | 參數多時可讀性會下降。 |
| Lambda | 需要臨時包裝參數或內嵌短邏輯。 | 過長會降低可讀性。 |
場景六:取號文字格式化
Func 是內建委派型別,適合簡單方法格式。這個範例用 Func(Of Integer, String) 保存不同的取號格式方法。
需要的主控項
TextBoxQueueNo:輸入號碼。ComboBoxQueueStyle:選擇格式。ButtonFormatQueueNo:格式化。LabelQueueResult:顯示結果。
範例程式碼
Public Class Form1
Private queueFormatter As Func(Of Integer, String)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ComboBoxQueueStyle.Items.Clear()
ComboBoxQueueStyle.Items.Add("一般號碼")
ComboBoxQueueStyle.Items.Add("客服號碼")
ComboBoxQueueStyle.SelectedIndex = 0
End Sub
Private Sub ComboBoxQueueStyle_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBoxQueueStyle.SelectedIndexChanged
If ComboBoxQueueStyle.SelectedIndex = 0 Then
queueFormatter = AddressOf FormatNormalQueueNo
Else
queueFormatter = AddressOf FormatServiceQueueNo
End If
End Sub
Private Sub ButtonFormatQueueNo_Click(sender As Object, e As EventArgs) Handles ButtonFormatQueueNo.Click
Dim queueNo As Integer
If Not Integer.TryParse(TextBoxQueueNo.Text.Trim(), queueNo) OrElse queueNo <= 0 Then
LabelQueueResult.Text = "請輸入大於 0 的號碼"
Return
End If
LabelQueueResult.Text = queueFormatter(queueNo)
End Sub
Private Function FormatNormalQueueNo(queueNo As Integer) As String
Return "NO-" & queueNo.ToString("000")
End Function
Private Function FormatServiceQueueNo(queueNo As Integer) As String
Return "SERVICE-" & queueNo.ToString("0000")
End Function
End Class
邏輯解析
Func(Of Integer, String)代表接收整數並回傳字串的方法格式。AddressOf FormatServiceQueueNo指向既有格式化方法。- 若委派用途有強烈業務語意,自訂委派名稱通常更清楚。
Lambda 適用情境:當事件方法需要額外固定參數時,AddressOf 不能直接帶參數,通常改用 Lambda 包裝。例如 AddHandler ButtonQuick.Click, Sub(sender, e) ApplyDiscount(50)。
實務判斷與常見誤區
常見問題整理
- 把
AddressOf Method()當成語法:AddressOf後面放方法名稱,不加括號。 - 把 AddressOf 當成立即執行:它只是建立委派,呼叫委派時才會執行方法。
- 忽略簽章相容性:方法參數與回傳值應符合目標委派。
- 重複 AddHandler:同一個處理器可能被呼叫多次,應避免重複註冊。
- 忘記 RemoveHandler:動態綁定後若不再需要,應適時移除。
- 用 AddressOf 硬包參數:需要額外參數時,Lambda 通常更合適。
- 把耗時工作放進事件處理器:AddressOf 只是指定方法,不會自動讓方法變成背景工作。
| 需求 | 建議寫法 | 原因 |
|---|---|---|
| 固定事件處理 | Handles |
最簡潔,適合設計階段就固定的事件。 |
| 動態事件綁定 | AddHandler ... AddressOf ... |
可在執行期間依條件加入處理器。 |
| 動態切換方法 | 自訂委派或 Func / Action |
可把目前策略保存成變數。 |
| 需要額外固定參數 | Lambda | AddressOf 不能直接附帶參數。 |
| 背景執行緒入口 | New Thread(AddressOf Method) |
Thread 需要符合格式的方法入口。 |
重點整理
AddressOf用來把既有方法轉成委派,不會立即執行方法。AddressOf後面放方法名稱,不加括號,也不傳入參數。- 方法必須和目標委派的參數與回傳值相容。
- 自訂委派適合語意明確的業務流程。
Action適合無回傳值方法,Func適合有回傳值方法。AddHandler搭配AddressOf可在執行期間動態綁定事件。RemoveHandler可移除已加入的事件處理方法,避免不必要的重複觸發。- 需要臨時包裝參數或內嵌短邏輯時,Lambda 通常比
AddressOf更合適。