.NET事件監(jiān)聽機(jī)制的局限與擴(kuò)展分析
本文實(shí)例分析了.NET事件監(jiān)聽機(jī)制的局限與擴(kuò)展。分享給大家供大家參考。具體分析如下:
.NET中把“事件”看作一個(gè)基本的編程概念,并提供了非常優(yōu)美的語法支持,對比如下C#和Java代碼可以看出兩種語言設(shè)計(jì)思想之間的差異。
someButton.Click += OnSomeButtonClick;
someButton.addActionListener(
new ActionListener(){
public void actionPerformed(){
...
}
});
在我們的軟件中就大量使用事件來對監(jiān)聽者與發(fā)布者解耦,但也遇到了一些局限,在這里跟大家分享一二。一是無法保證監(jiān)聽者的調(diào)用順序;二是當(dāng)監(jiān)聽者很多時(shí)的監(jiān)聽、解除監(jiān)聽的效率問題。
事件監(jiān)聽者的調(diào)用順序
.NET的事件監(jiān)聽機(jī)制對監(jiān)聽者的調(diào)用順序沒有明確的保證,但有時(shí)我們卻要求保證不同組件之間的處理順序。比如,在我們的軟件中使用類似解釋器模式的方式來實(shí)現(xiàn)用戶交互操作,一個(gè)稱作交互源的組件負(fù)責(zé)將UI控件上的事件分派給一組稱為交互器的組件,這些組件依照事先確定的優(yōu)先級依次獲得事件處理的機(jī)會,只有當(dāng)具有高優(yōu)先級的交互器沒有處理事件時(shí),低優(yōu)先級的組件才能執(zhí)行進(jìn)一步的處理。這樣,我們就能在不同業(yè)務(wù)功能的實(shí)現(xiàn)中通過以不同的順序組織交互器來重用它們。比如,重用一些基本的視圖縮放、平移、菜單處理等功能。
在上述場景下,如何保證交互器間事件處理的順序就變得很重要了。當(dāng)然如果你看一下MulticastDelegate的源代碼的話,可以知道在當(dāng)前的實(shí)現(xiàn)中其實(shí)各個(gè)監(jiān)聽者還是有一定的調(diào)用順序的。但一來這屬于實(shí)現(xiàn)細(xì)節(jié),在將來完全可能改變;二來如果不同的監(jiān)聽器位于不同的模塊中時(shí),要依賴于這一實(shí)現(xiàn)而保證它們之間的調(diào)用順序也是很困難的。
在這里我們借鑒了Java中以接口進(jìn)行事件處理的方式,并在添加監(jiān)聽器的同時(shí)接收一個(gè)表示優(yōu)先級的參數(shù),這樣就可以明確的維護(hù)各個(gè)監(jiān)聽器的順序了,如下面的代碼所示。我們在交互器(IInteractor)接口中為每一個(gè)UI事件定義了相應(yīng)的方法,并且讓InteractSource負(fù)責(zé)將控件上的事件轉(zhuǎn)化為對接口中相應(yīng)方法的調(diào)用。
{
public void AddInteractor(int priority, IInteractor interactor)
{
}
}
public interface IInteractor
{
public void OnMouseDown(MouseEventArgs e)
{
}
... ...
}
監(jiān)聽器添加與移除的效率
MulticastDelegate是我們平常使用的事件(event)機(jī)制背后的實(shí)現(xiàn),通過其源代碼可以看到,它在內(nèi)部使用數(shù)組保存了對各個(gè)監(jiān)聽器的引用。這就會造成一個(gè)問題——當(dāng)對一個(gè)事件的監(jiān)聽器數(shù)目很多時(shí),添加和移除監(jiān)聽器的效率將會變得非常低。以移除為例,對于有N個(gè)監(jiān)聽器的事件來說,平均要進(jìn)行N/2次比較才能確定監(jiān)聽器的位置,而且還要有額外的數(shù)組整理操作。為了解決這一情況,我們先是嘗試自行定義事件的添加、移除邏輯,并在內(nèi)部嘗試使用字典、哈希表等多種方式進(jìn)行存儲,但事實(shí)證明,雖然二者在時(shí)間復(fù)雜度上有優(yōu)勢,不過其實(shí)際效率還是達(dá)不到要求。
最好狀態(tài)下是要有一種能在常數(shù)時(shí)間內(nèi)添加和移除監(jiān)聽器的數(shù)據(jù)結(jié)構(gòu),也許你也想到了——雙向鏈表。
也許你又想到了——在雙向鏈表中添加和刪除是常數(shù)時(shí)間,但查找卻仍然是O(n)的復(fù)雜度。
使用接口形式的設(shè)計(jì)方式再次展現(xiàn)了其靈活性,我們可以將事件發(fā)布者的設(shè)計(jì)為如下形式(示意代碼):
{
private LinkedList list = new LinkedList();
public Tocken AddListener(IEventListener listener)
{
LinkedListNode n = new LinkedListNode(listener);
list.AddLast(n);
return new Tocken(node);
}
public void RemoveListener(Tocken tocken)
{
list.Remoe(tocken.node);
}
public class Tocken
{
internal LinkedListNode node;
}
}
在此類中使用雙向鏈表存儲已經(jīng)添加的監(jiān)聽器,而在AddListener方法每次調(diào)用時(shí)都將所添加的鏈表節(jié)點(diǎn)保存到一個(gè)令牌(Token)中返回。監(jiān)聽者需要保存這個(gè)令牌,并使用它來解除監(jiān)聽。當(dāng)然,監(jiān)聽者完全可以忽略令牌是個(gè)什么東西,就像地鐵票從來就是只是一張票而已,我們不曾關(guān)心它包含著什么信息。不過對于發(fā)布者來說卻可以將一些定位信息保存在其中,從而在解除監(jiān)聽時(shí)充分利用,在上面的代碼中我就保存了鏈表節(jié)點(diǎn)的引用,從而達(dá)到監(jiān)聽者的添加、定位、移除都在常數(shù)時(shí)間內(nèi)完成。
當(dāng)然,還可以在Tocken中保存發(fā)布者的引用,這樣就可以發(fā)現(xiàn)”取消對一個(gè)從來沒有監(jiān)聽過的對象的監(jiān)聽“這樣的BUG?;蛘?,還有其它信息。
希望本文所述對大家的C#程序設(shè)計(jì)有所幫助。
相關(guān)文章
Asp.net 圖片文件防盜鏈(尊重勞動成果)及BeginRequest事件學(xué)習(xí)
關(guān)于圖片盜鏈這個(gè)問題,畢竟是自己的勞動成功,很多人不希望別人就那么輕易地偷走了;反盜鏈的程序其實(shí)很簡單,熟悉ASP.NET 應(yīng)用程序生命周期的話很容易就可以寫一個(gè),運(yùn)用HttpModule在BeginRequest事件中攔截請求就ok了2013-01-01.net設(shè)計(jì)模式之裝飾模式(Decorator)
這篇文章主要為大家詳細(xì)介紹了.net設(shè)計(jì)模式之裝飾模式Decorator,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06此頁的狀態(tài)信息無效,可能已損壞 的處理辦法及原因分析
此頁的狀態(tài)信息無效,可能已損壞 的處理辦法及原因分析,需要的朋友可以參考一下2013-06-06Asp.Net Core利用文件監(jiān)視進(jìn)行快速測試開發(fā)詳解
這篇文章主要給大家介紹了關(guān)于Asp.Net Core利用文件監(jiān)視進(jìn)行快速測試開發(fā)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12使用SWFUpload實(shí)現(xiàn)無刷新上傳圖片
現(xiàn)在網(wǎng)站不再講究的是功能性,更多的是用戶體驗(yàn)性,在這里上傳圖片就需要用到ajax無刷新上傳圖片,這里面包含的東西不是一點(diǎn)半點(diǎn) 。這里用到的是一個(gè)插件swfupload 實(shí)現(xiàn)無刷新上傳圖片2015-06-06Entity?Framework管理一對一實(shí)體關(guān)系
本文詳細(xì)講解了Entity?Framework管理一對一實(shí)體關(guān)系的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03asp.net 文件上傳 實(shí)時(shí)進(jìn)度
在swfupload的基礎(chǔ)上增加一些個(gè)性化東西.附圖2張.2009-11-11.NET Core Dapper操作mysql數(shù)據(jù)庫的實(shí)現(xiàn)方法
這篇文章主要介紹了.NET Core Dapper操作mysql數(shù)據(jù)庫的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04