本月的“準系統”專欄建立在本人上兩期專欄的基礎之上,在上兩期“準系統”專欄中,我討論了與委託相關的概念和編程技巧。本文假定讀者已經閱讀了該專欄的上兩期,並且理解委託在 Microsoft.NET Framework 中所扮演的角色。如果您尚未閱讀上兩期專欄,請參閱 Implementing Callback Notifications Using Delegates 和 Implementing Callbacks with a Multicast Delegate。您還應該知道如何設計和編寫使用多路廣播委託將回調通知發送到一組處理常式方法的簡單應用程式。
您可能已經對事件進行編程若干年了,但是遷移到 .NET Framework 仍然需要您重新檢查事件的內部工作,因為 .NET Framework 中的事件位於委託的頂層。對委託瞭解得越多,對事件進行編程時所具有的駕馭能力就越強。在開始使用公用語言運行庫 (CLR) 中的一個事件驅動架構(例如 Windows Forms 或 ASP.NET)時,理解事件在較低的層級如何工作至關重要。本月,我的目標是使您理解事件在較低的層級如何工作。
事件究竟是什嗎?
事件只是一種形式化的軟體模式,在該模式中,通知源對一個或多個處理常式方法進行回調。因此,事件類別似於介面和委託,因為它們提供了設計使用回調方法的應用程式的方法。但是,事件大大提高了工作效率,因為它們使用起來比介面或委託更容易。事件允許編譯器和 Visual Studio.NET IDE 在幕後為您做大量的工作。
Class BankAccount
Public Event LargeWithdraw As LargeWithdrawHandler
Sub Withdraw(ByVal Amount As Decimal)
'*** send notifications if required
If (Amount > 5000) Then
RaiseEvent LargeWithdraw(Amount)
End If
'*** perform withdrawal
End Sub
End Class
但是,如果您願意根據 LargeWithdrawEvent 私人欄位進行編程,則可以用更適當的方式來處理事件處理常式引發的異常。請查看圖 3 中的代碼。正如您所看到的那樣,降至一個較低的層級並根據該私人委託欄位進行編程可以提供額外的控制層級。您可以恰當地處理異常,然後繼續執行隨後出現在列表中的事件處理常式。與 RaiseEvent 文法相比,該方法具有明顯的優勢,在 RaiseEvent 文法中,一個事件處理常式引發的異常將阻止執行隨後出現在調用列表中的任何事件處理常式。
Figure 3 Using the Private Delegate Field
Sub Withdraw(ByVal Amount As Decimal)
'*** send notifications if required
If (Amount > 5000) AndAslo (Not LargeWithdrawEvent Is Nothing) Then
Dim handler As LargeWithdrawHandler
For Each handler In LargeWithdrawEvent.GetInvocationList()
Try
handler.Invoke(Amount)
Catch ex As Exception
'*** deal with exceptions as they occur
End Try
Next
End If
'*** perform withdrawal
End Sub
現在,您已經瞭解了使用事件實現回調設計所需的所有步驟。圖 4 中的代碼顯示了一個完整的應用程式,在該應用程式中,已經註冊了兩個事件處理常式,以接收來自 BankAccount 對象的 LargeWithdraw 事件的回調通知。
Figure 4 An Event-based Design for Callback Notifications
Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)
Class BankAccount
Public Event LargeWithdraw As LargeWithdrawHandler
Sub Withdraw(ByVal Amount As Decimal)
'*** send notifications if required
If (Amount > 5000) Then
RaiseEvent LargeWithdraw(Amount)
End If
'*** perform withdrawal
End Sub
End Class
Class AccountHandlers
Shared Sub LogWithdraw(ByVal Amount As Decimal)
'*** write withdrawal info to log file
End Sub
Shared Sub GetApproval(ByVal Amount As Decimal)
'*** block until manager approval
End Sub
End Class
Module MyApp
Sub Main()
'*** create bank account object
Dim account1 As New BankAccount()
'*** register event handlers
AddHandler account1.LargeWithdraw, _
AddressOf AccountHandlers.LogWithdraw
AddHandler account1.LargeWithdraw, _
AddressOf AccountHandlers.GetApproval
'*** do something that triggers callback
account1.Withdraw(5001)
End Sub
End Module