詳談VB.NET事件編程
可能大家使用VB.NET事件編程已經多年了,當你對委托了解得越深,使用事件編程時就越能利用其強大的功能。在使用通用語言運行的事件驅動框架工作時,了解事件在低層是怎樣工作的很重要。本文的目標就是讓你了解事件在低層是怎樣工作的。事件到底是什么?
事件僅僅是一種軟件模式(pattern),在事件中通知源對一個或多個處理方法進行回調(callback)。因此事件與接口(interface)和委托(delegate)相似,因為它們都提供了一條途徑來設計使用回調方法的應用程序。但是事件生產率更高,因為它比接口和委托更易使用。事件讓編譯器和Visual Studio .NET集成開發(fā)環(huán)境在后臺為你做了很多工作。
包含事件的設計是基于一個事件源和一個或多個處理程序的。事件源可以是類或對象,事件處理程序是綁定到某個處理方法的委托對象。圖1在較高層次顯示了數(shù)據(jù)源與處理方法的聯(lián)系。
圖1.VB.NET事件編程之事件源和處理程序
每個事件都根據(jù)特定的委托類型定義。對于每個事件源定義的事件,都有一個基于事件下面的委托類型的專用字段,該字段用于跟蹤多點傳送的委托對象。事件源也提供了一個公共的注冊方法,讓你可以注冊希望的事件處理程序。
當你建立一個事件處理程序(一個委托對象)并把它與事件源一起注冊時,事件源簡單地把新的事件處理程序添加到列表的結尾。接著事件源能使用專用字段調用多點傳送委托對象的Invoke方法,該方法將執(zhí)行所有已注冊的事件處理程序。事件真正好的地方是大多數(shù)設置工作已經被開發(fā)環(huán)境完成。你將看到,Visual Basic .NET編譯器幫助你在定義事件時自動的添加一個私有字段和一個公共注冊方法。你也會看到Visual Studio .NET通過自動生成處理方法的框架定義的代碼生成器為你提供了更多幫助。
使用VB.NET事件編程
由于在.NET中的事件建立在委托的頂層,所有它們下面的通道細節(jié)與早期版本的Visual Basic工作方式有很大的不同。但是Visual Basic .NET語言的設計者為了保持與早期Visual Basic版本語言的一致性做了大量的工作。在很多情況中,事件編程使用與原來相近的語法。例如,你將使用Event、 RaiseEvent和WithEvents等關鍵字,它們的行為與早期版本的相同。
我們建立一個簡單的基于事件的回調設計。首先我需要使用Event關鍵字在類的定義中定義一個事件。事件必須根據(jù)特定的委托類型來定義。下面是一個委托類型定義和使用它定義事件的類:
- Delegate Sub LargeWithdrawHandler(ByVal Amount As Decimal)
- Class BankAccount
- Public Event LargeWithdraw As LargeWithdrawHandler
- '省略了其它成員
- End Class
在上面的例子中,LargeWithdraw事件被定義為一個實例成員。該設計中BankAccount對象將作為事件源。如果你希望用類代替對象作為事件源,你可以使用Shared關鍵字把事件定義為共享成員。
當使用事件編程時,肯定編譯器在后臺做了大量的工作也很重要。例如,當編譯BankAccount類的定義時編譯器做了什么?圖2顯示了使用ILDasm.exe(中間語言反編譯器)查看類的定義結果。
圖2. ILDasm中的類定義
當你定義事件時,編譯器在類的定義中產生四個成員。第一個成員是基于委托類型的私有字段,它用于跟蹤一個委托對象的引用。編譯器產生的該私有字段的名字是事件字加上"Event"標識。這意味著建立LargeWithdraw事件的結果是建立了名為LargeWithdrawEvent的私有字段。
編譯器也產生了兩個方法幫助注冊和取消注冊作為事件處理程序服務的委托對象。這兩個方法的命名使用了標準的命名轉換。注冊事件處理程序的方法的名字加了"add_"前綴,取消注冊的方法前面加了"remove_"前綴。因此為LargeWithdraw事件建立的這兩個方法名稱為add_LargeWithdraw 和remove_LargeWithdraw。
Visual Basic .NET編譯器為add_LargeWithdraw產生了實現(xiàn)代碼,它接收以一個委托對象作為參數(shù)并通過調用委托類的Combine方法將它添加到處理程序列表。編譯器也產生remove_LargeWithdraw的實現(xiàn)代碼,它通過調用委托類的Remove方法從列表中刪除一個處理方法。添加到類定義中的第四個成員表現(xiàn)了事件本身。你能在圖2中定位名為LargeWithdraw的事件成員。它有一個向下的三角形。但是你必須注意這個事件成員不是一個真的與其它三個相似的物理事件,它是一個元數(shù)據(jù)成員。該元數(shù)據(jù)事件成員是有價值的,因為它通知編譯器和其它開發(fā)工具該類支持.NET框架中的事件注冊標準模式。該事件成員也包含注冊和反注冊方法的名字。這使Visual Basic .NET和C#等可管理語言的編譯器能在編譯時就發(fā)現(xiàn)注冊方法的名稱。當Visual Basic .NET發(fā)現(xiàn)類定義中包含事件時,它自動在產生事件處理程序的注冊代碼時生成該處理方法的框架定義。
在討論引發(fā)事件前,我將講解建立用于定義事件的委托類型所涉及的限制。定義事件的委托類型不能有返回值,你必須使用Sub關鍵字而不能使用Function關鍵字:
- '能被事件使用
- Delegate Sub BaggageHandler()
- Delegate Sub MailHandler(ItemID As Integer)
- '不能被事件使用
- Delegate Function QuoteOfTheDayHandler(Funny As Boolean) As String
該限制有一個很好的原因。在綁定到多個處理方法的多點傳送委托中使用返回值非常困難。多點傳送委托的Invoke調用返回的值是調用列表中最后一個處理方法的值。可是捕獲列表中前面的處理方法的返回值就不直接了,消除捕獲多個返回值的需求使事件更容易使用。
【編輯推薦】