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