亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

使用 .NET 對事件進行編程

系統 1519 0
作者:Ted Pattison  

您可能已經對事件進行編程若干年了,但是遷移到 .NET Framework 仍然需要您重新檢查事件的內部工作,因為 .NET Framework 中的事件位于委托的頂層。 對委托的了解越多,對事件進行編程時所具有的駕馭能力越強。 開始使用公共語言運行庫 (CLR) 的某個事件驅動框架(例如 Windows? Forms 或 ASP.NET)時,理解事件在較低的級別如何工作至關重要。 本月我的目標是使您理解事件在較低的級別如何工作。

什么是事件?

事件是一種形式化的軟件模式,在該模式中,通知源將對一個或多個處理程序方法進行回調。 因此,事件類似于接口和委托,因為它們提供了設計使用回調方法的應用程序的方法。 但是,事件極大地提高了工作效率,因為它們使用起來比接口或委托更容易。 事件允許編譯器和 Visual Studio? .NET IDE 在幕后為您做大量的工作。

涉及事件的設計基于事件源和一個或多個事件處理程序。 事件源可以是一個類也可以是一個對象。 事件處理程序是綁定到處理程序方法的委托對象。 圖 1 顯示了綁定到其處理程序方法的事件源的高級別視圖。


圖 1 事件源和處理程序

每個事件都是根據特定的委托類型定義的。 對于事件源定義的每個事件,有一個基于事件的基礎委托類型的私有字段。 該字段用于跟蹤多路廣播委托對象。 事件源還提供允許您注冊所需數量的事件處理程序的公用注冊方法。

當您創建事件處理程序(委托對象)并在事件源中注冊它時,事件源只是將新的事件處理程序追加到列表的結尾。 然后,事件源可以使用私有字段在多路廣播委托上調用 Invoke,該多路廣播委托將依次執行所有注冊的事件處理程序。

事件的真正的妙處在于對其進行設置的大量工作都已經為您做好了。 正如您很快就會看到的,無論任何時候您定義事件時,Visual Basic? .NET 編譯器都會通過自動添加私有委托字段和公用注冊方法幫助您工作。 您還將看到 Visual Studio .NET 可以通過代碼生成器提供更多的幫助,代碼生成器可以自動發出適用于您的處理程序方法的主干定義。

對事件進行編程

由于 .NET 中的事件建立在委托的頂層,因此它們的基礎的管道詳細信息與較低版本的 Visual Basic 中所一直使用的截然不同。 但是,Visual Basic .NET 的語言設計者們在保持事件編程的語法與較低版本的 Visual Basic 一致方面做得很好。 在很多情況下,對事件進行編程涉及的語法與您習慣使用的熟悉的老語法相同。 例如,您將使用 Event、RaiseEvent 和 WithEvents 等關鍵字,而它們的行為方式與其在較低版本的 Visual Basic 中的行為方式幾乎完全相同。

讓我們通過創建一個基于事件的簡單的回調設計開始。 首先,我需要通過使用 Event 關鍵字在類定義內定義一個事件。 必須根據特定的委托類型定義每個事件。 下面是定義自定義委托類型和用來定義事件的類的一個示例:

              Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)

Class BankAccount
  Public Event LargeWithdraw As LargeWithdrawHandler
  '*** other members omitted
End Class

            

在本示例中,LargeWithdraw 事件被定義為實例成員。 在本設計中,BankAccount 對象將充當事件源。 如果希望類而不是對象充當事件源,應該使用 Shared 關鍵字將事件定義為共享成員。

對事件進行編程時,知道編譯器在幕后為您做了大量額外的工作這一點很重要。 例如,當您將我剛才向給您看過的 BankAccount 類的定義編譯到程序集時,您認為編譯器會做什么? 圖 2 顯示了在中間語言反匯編程序 ILDasm.exe 中檢查生成的類定義時,該定義是什么樣的。 該視圖毫無保留地向您顯示了 Visual Basic .NET 編譯器在幕后做了多少工作來幫助您。


圖 2 ILDasm 中的類定義

當您定義事件時,編譯器在類定義內生成四個成員。 第一個成員是基于委托類型的私有字段。 該字段用于跟蹤對委托對象的引用。 編譯器通過采用事件本身的名稱并添加后綴“Event”生成該私有字段的名稱。 這意味著創建名為 LargeWithdraw 的事件將導致創建名為 LargeWithdrawEvent 的私有字段。

編譯器還生成兩個方法,幫助注冊和注銷將成為事件處理程序的委托對象。 這兩個方法使用標準的命名規則進行命名。 用于注冊事件處理程序的方法使用事件的名稱,并帶有前綴“add_”。 用于注銷事件處理程序的方法使用事件的名稱,并帶有前綴“remove_”。 因此,為 LargeWithdraw 事件創建的兩個方法名為 add_LargeWithdraw 和 remove_LargeWithdraw。

Visual Basic .NET 編譯器通過調用 Delegate 類的 Combine 方法為將委托對象作為參數接受并將其添加到處理程序列表中的 add_LargeWithdraw 生成一個實現。 編譯器通過在 Delegate 類中調用 Remove 方法為從列表中刪除一個處理程序方法的 remove_LargeWithdraw 生成一個實現。

第四個也是最后一個添加到類定義中的成員是表示事件本身的成員。 在圖 2 中,您應該能夠找到名為 LargeWithdraw 的事件成員。 它是旁邊帶有一個倒三角的成員。 但是,您應該注意到,該事件并不象其它三個成員一樣真的是一個物理成員。 相反,它是一個僅包含元數據的成員。

此僅包含元數據的事件成員很有價值,因為它可以向該類支持的編譯器和其他開發工具通知 .NET Framework 中事件注冊的標準模式。 該事件成員還包含注冊方法和注銷方法的名稱。 這使得 Visual Basic .NET 和 C# 等托管語言的編譯器可以在編譯時查找注冊方法的名稱。

Visual Studio .NET 是查找此僅包含元數據的事件成員的開發工具的另一個很好的示例。 當 Visual Studio .NET 發現類定義包含事件時,它將自動生成處理程序方法的主干定義以及將它們作為事件處理程序進行注冊的代碼。

在開始討論激發事件之前,我想提出一個有關用于定義事件的委托類型的限制。 用于定義事件的委托類型不能有返回值。 您必須使用 Sub 關鍵字而不是 Function 關鍵字定義委托類型,如下所示:

              '*** can be used for events
Delegate Sub BaggageHandler()
Delegate Sub MailHandler(ItemID As Integer)

'*** cannot be used for events
Delegate Function QuoteOfTheDayHandler(Funny As Boolean) As String

            

對此限制有很充分的原因。 當涉及與若干處理程序方法綁定的多路廣播委托時,處理返回值相當困難。 在多路廣播委托上調用 Invoke 返回與調用列表中的最后一個處理程序方法相同的值。 但是,捕獲較早在列表中出現的處理程序方法的返回值并不那么簡單。 不需要捕獲多個返回值只會使事件更容易使用。

激發事件

現在,讓我們修改 BankAccount 類使其在提款數量超出 $5000 閾值時能夠激發一個事件。 激發 LargeWithdraw 事件的最簡單的方法是在一個方法、屬性或構造函數的實現中使用 RaiseEvent 關鍵字。 您可能會覺得該語法很熟悉,因為它類似于您在較低版本的 Visual Basic 中使用的語法。 下面是從 Withdraw 方法激發 LargeWithdraw 事件的一個示例:

              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

            

雖然語法與較低版本的 Visual Basic 相同,但是激發事件時所發生的事情則與現在截然不同。 使用 RaiseEvent 關鍵字激發事件時,Visual Basic .NET 編譯器生成執行每個事件處理程序所需的代碼。 例如,當您編譯以下代碼時您認為會出現什么情況?

      RaiseEvent LargeWithdraw(Amount)

    

Visual Basic .NET 編譯器將此表達式擴展為在保留多路廣播委托對象的私有字段上調用 Invoke 的代碼。 換句話說,使用 RaiseEvent 關鍵字與在以下 snippet 中編寫代碼具有完全相同的效果:

      If (Not LargeWithdrawEvent Is Nothing) Then
  LargeWithdrawEvent.Invoke(Amount)
End If

    

注意,Visual Basic .NET 編譯器生成的代碼執行檢查以確保 LargeWithdrawEvent 字段包含對某個對象的有效引用。 這是因為 LargeWithdrawEvent 字段的值在第一個處理程序方法注冊之前一直為 Nothing。 因此,除非當前至少有一個處理程序方法已注冊,否則生成的代碼并不嘗試調用 Invoke。

您應該能夠對激發事件進行觀察。 使用 RaiseEvent 關鍵字或者根據編譯器自動生成的 LargeWithdrawEvent 私有字段直接進行編程通常并沒有什么分別。 兩種方法都生成相同的代碼:

      '*** this code
RaiseEvent LargeWithdraw(Amount)

'*** is the same as this code
If (Not LargeWithdrawEvent Is Nothing) Then
  LargeWithdrawEvent.Invoke(Amount)
End If

    

在很多情況下,您可能喜歡使用 RaiseEvent 關鍵字語法,因為它需要的鍵入較少,生成的代碼較簡潔。 但是,在某些情況下,當您需要較多控制時,根據 LargeWithdrawEvent 私有字段進行明確編程可能會有意義。 讓我們看一個這種情況的示例。

想象以下情況:BankAccount 對象有三個事件處理程序已注冊以接收 LargeWithdraw 事件的通知。 如果使用 RaiseEvent 關鍵字觸發事件并且調用列表中的第二個事件處理程序出現異常,將會出現什么情況? 包含 RaiseEvent 語句的代碼行將接收運行時異常,但是您可能沒辦法確定哪個事件處理程序導致異常。 而且,可能沒有辦法處理第二個事件處理程序導致的異常,也沒有辦法按預期方式在執行第三個事件處理程序的位置繼續進行。

但是,如果您愿意根據 LargeWithdrawEvent 私有字段進行編程,則可以更適當的方式處理事件處理程序導致的異常。 檢查 圖 3 中的代碼。 正如您所看到的,降至一個較低的級別并根據該私有委托字段進行編程可以提供額外程度的控制。 您可以恰當地處理異常,然后繼續執行較晚出現在列表中的事件處理程序。 與 RaiseEvent 語法相比,該方法具有明顯的好處,在該方法中一個事件處理程序導致的異常將阻止執行較晚出現在調用列表中的任何事件處理程序。

創建和注冊事件處理程序

現在,您已經知道如何定義和激發事件,該是討論如何創建事件處理程序并在給定源中注冊它的時候了。 有兩種不同的方法可以在 Visual Basic .NET 中完成以上操作。 第一種方法稱為動態事件綁定,涉及 AddHandler 關鍵字的使用。 第二種方法稱為靜態事件綁定,涉及熟悉的 Visual Basic 關鍵字 WithEvents 的使用。 我打算在下一期討論靜態事件綁定。 所以現在,讓我們來看一看動態事件綁定的工作原理。

請記住,事件處理程序是一個委托對象。 因此,可以通過從事件所基于的委托類型實例化一個委托對象,創建一個事件處理程序。 創建此委托對象時,必須將其綁定到要成為事件處理程序的目標處理程序方法。

創建事件處理程序后,必須通過在事件源上調用特定的注冊方法在特定的事件中注冊它。 回憶一下,LargeWithdraw 事件的注冊方法名為 add_LargeWithdraw。 當您調用 add_LargeWithdraw 方法并將委托對象作為參數傳遞時,事件源將委托對象添加到將接收事件通知的事件處理程序列表中。

有關事件注冊會出現混淆的是您從未直接調用 add_LargeWithdraw 等方法。 實際上,如果您按名稱訪問事件注冊方法,Visual Basic .NET 編譯器將生成編譯時錯誤。 但是,您可以使用包括 AddHandler 語句的替代語法。 當您使用 AddHandler 語句時,Visual Basic .NET 編譯器生成為您調用事件注冊方法的代碼。

讓我們來看一個使用動態事件注冊綁定幾個事件處理程序的示例。 想象您已經在 AccountHandlers 類中編寫了以下共享方法的集合:

      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

    

如果要將這些方法用作 BankAccount 類的 LargeWithdraw 事件的事件處理程序,您應該做什么? 讓我們從創建綁定到處理程序 LogWithdraw 的事件處理程序開始。 首先,您必須創建將成為事件處理程序的委托對象:

      Dim handler1 As LargeWithdrawHandler
handler1 = AddressOf AccountHandlers.LogWithdraw

    

然后,您必須使用 AddHandler 語句在事件源中注冊該新的委托對象。 當您使用 AddHandler 語句注冊事件處理程序時,您需要傳遞兩個參數,類似以下內容:

      AddHandler <event>, <delegate object>

    

AddHandler 需要的第一個參數是對類或對象的事件進行求值的表達式。 第二個參數是對將被綁定為事件處理程序的委托對象的引用。 下面是使用 AddHandler 語句在 BankAccount 對象的 LargeWithdraw 事件中注冊事件處理程序的一個示例:

      '*** create bank account object
Dim account1 As New BankAccount()

'*** create and register event handler
Dim handler1 As LargeWithdrawHandler
handler1 = AddressOf AccountHandlers.LogWithdraw
AddHandler account1.LargeWithdraw, handler1

    

當您使用 AddHandler 關鍵字注冊 LargeWithdraw 事件的事件處理程序時,Visual Basic .NET 編譯器將擴展此代碼以調用注冊方法 add_LargeWithdraw。 執行包含 AddHandler 語句的代碼后,您的事件處理程序已就位,并已準備就緒可以進行通知。 因此,無論任何時候 BankAccount 對象激發 LargeWithdraw 事件時,都將執行 LogWithdraw 方法。

在上一示例中,我使用了較長形式的語法以便確切地說明創建和注冊事件處理程序時所發生的事情。 但是,明白了原理之后,您可能希望使用更簡潔的語法來實現同樣的目標,如下所示:

      '*** create bank account object
Dim account1 As New BankAccount()

'*** register event handlers
AddHandler account1.LargeWithdraw, AddressOf AccountHandlers.LogWithdraw
AddHandler account1.LargeWithdraw, AddressOf AccountHandlers.GetApproval

    

由于 AddHandler 語句期望將委托對象作為第二個參數引用,因此您可以使用 AddressOf 運算符的速記語法,后跟目標處理程序方法。 當 Visual Basic .NET 編譯器發現這種情況后,它就會生成額外的代碼以創建將成為事件處理程序的委托對象。

Visual Basic .NET 語言的 AddHandler 語句由 RemoveHandler 語句補充。 RemoveHandler 需要的兩個參數與 AddHandler 相同,但是它具有相反的效果。 它通過調用事件源提供的 remove_LargeWithdraw 方法從已注冊處理程序列表中刪除目標處理程序方法:

      Dim account1 As New BankAccount()

'*** register event handler
AddHandler account1.LargeWithdraw, AddressOf AccountHandlers.LogWithdraw

'*** unregister event handler
RemoveHandler account1.LargeWithdraw, AddressOf AccountHandlers.LogWithdraw

    

現在,您已經看到了使用事件實現回調設計所需的所有步驟。上面的代碼顯示了一個完整的應用程序,在該應用程序中兩個事件處理程序已注冊以接收來自 BankAccount 對象的 LargeWithdraw 事件的回調通知。

小結

雖然使用事件的動機和某些語法與較低版本的 Visual Basic 相比都沒有變,但是,您必須承認現在情況有些不同了。 正如您所看到的,您對如何響應事件的控制能力比以前更強了。 如果您希望降低級別并根據委托進行編程,則更是如此。

在下一期的 Basic Instincts 欄目中,我打算繼續有關事件的此討論。 我將向您說明 Visual Basic .NET 如何通過您熟悉的 WithEvents 關鍵字語法支持靜態事件綁定,并將討論 Handles 子句。 要真正控制事件,您必須能夠輕松駕馭動態事件注冊和靜態事件注冊。

使用 .NET 對事件進行編程


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 成年女人免费 | 97久久影院| 久久久成人网 | 免费两性的视频网站 | 欧美一区二区三区黄色 | 亚洲婷婷综合中文字幕第一页 | 玖玖在线免费视频 | 成人国产精品视频频 | 成人爽视频 | 日本高清h色视频在线观看 日本高清不卡二区 | 亚洲va欧美va国产综合久久 | 天天干天操| 成人黄色一级片 | 日本一区二区三区免费在线观看 | 人人干天天操 | 成人特黄午夜性a一级毛片 成人特级毛片69免费观看 | 五月婷综合网 | 日韩亚洲精品不卡在线 | 亚洲精品中文字幕乱码三区一二 | 亚洲精品久久九九精品 | 亚欧视频在线观看 | 久草在线最新 | 色综合久久天天影视网 | 亚洲国内精品久久 | 日韩欧美 在线播放 | 成年人香蕉视频 | 日日夜夜综合 | 国产精品揄拍一区二区久久 | 国产成人精品午夜二三区 | 国模极品一区二区三区 | 99热成人精品国产免男男 | 久久免费资源 | 成人欧美一区二区三区视频xxx | 99久久精品国产综合男同 | 亚洲欧美一区二区三区国产精品 | 国产伦精品一区二区三区免费观看 | 欧美成人免费观看国产 | 九九伦理 | 日韩欧美成人免费中文字幕 | 国产在线欧美精品 | 国内精品久久久久久影院网站小说 |