程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> ASP.NET >> ASP.NET基礎 >> .NET事件監聽機制的局限與擴展分析

.NET事件監聽機制的局限與擴展分析

編輯:ASP.NET基礎

本文實例分析了.NET事件監聽機制的局限與擴展。分享給大家供大家參考。具體分析如下:

.NET中把“事件”看作一個基本的編程概念,並提供了非常優美的語法支持,對比如下C#和Java代碼可以看出兩種語言設計思想之間的差異。
復制代碼 代碼如下:// C#
someButton.Click += OnSomeButtonClick;
復制代碼 代碼如下:// Java
someButton.addActionListener(
    new ActionListener(){
        public void actionPerformed(){
            ...
        }
});

在我們的軟件中就大量使用事件來對監聽者與發布者解耦,但也遇到了一些局限,在這裡跟大家分享一二。一是無法保證監聽者的調用順序;二是當監聽者很多時的監聽、解除監聽的效率問題。
 
事件監聽者的調用順序

.NET的事件監聽機制對監聽者的調用順序沒有明確的保證,但有時我們卻要求保證不同組件之間的處理順序。比如,在我們的軟件中使用類似解釋器模式的方式來實現用戶交互操作,一個稱作交互源的組件負責將UI控件上的事件分派給一組稱為交互器的組件,這些組件依照事先確定的優先級依次獲得事件處理的機會,只有當具有高優先級的交互器沒有處理事件時,低優先級的組件才能執行進一步的處理。這樣,我們就能在不同業務功能的實現中通過以不同的順序組織交互器來重用它們。比如,重用一些基本的視圖縮放、平移、菜單處理等功能。
 
在上述場景下,如何保證交互器間事件處理的順序就變得很重要了。當然如果你看一下MulticastDelegate的源代碼的話,可以知道在當前的實現中其實各個監聽者還是有一定的調用順序的。但一來這屬於實現細節,在將來完全可能改變;二來如果不同的監聽器位於不同的模塊中時,要依賴於這一實現而保證它們之間的調用順序也是很困難的。
 
在這裡我們借鑒了Java中以接口進行事件處理的方式,並在添加監聽器的同時接收一個表示優先級的參數,這樣就可以明確的維護各個監聽器的順序了,如下面的代碼所示。我們在交互器(IInteractor)接口中為每一個UI事件定義了相應的方法,並且讓InteractSource負責將控件上的事件轉化為對接口中相應方法的調用。
復制代碼 代碼如下:public class InteractSource
{
    public void AddInteractor(int priority, IInteractor interactor)
    {
    }
}
 
public interface IInteractor
{
    public void OnMouseDown(MouseEventArgs e)
    {
    }
   
    ... ...
}

監聽器添加與移除的效率

MulticastDelegate是我們平常使用的事件(event)機制背後的實現,通過其源代碼可以看到,它在內部使用數組保存了對各個監聽器的引用。這就會造成一個問題——當對一個事件的監聽器數目很多時,添加和移除監聽器的效率將會變得非常低。以移除為例,對於有N個監聽器的事件來說,平均要進行N/2次比較才能確定監聽器的位置,而且還要有額外的數組整理操作。為了解決這一情況,我們先是嘗試自行定義事件的添加、移除邏輯,並在內部嘗試使用字典、哈希表等多種方式進行存儲,但事實證明,雖然二者在時間復雜度上有優勢,不過其實際效率還是達不到要求。
 
最好狀態下是要有一種能在常數時間內添加和移除監聽器的數據結構,也許你也想到了——雙向鏈表。
 
也許你又想到了——在雙向鏈表中添加和刪除是常數時間,但查找卻仍然是O(n)的復雜度。
 
使用接口形式的設計方式再次展現了其靈活性,我們可以將事件發布者的設計為如下形式(示意代碼):
復制代碼 代碼如下:public class EventSource
{
    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;
    }
}

在此類中使用雙向鏈表存儲已經添加的監聽器,而在AddListener方法每次調用時都將所添加的鏈表節點保存到一個令牌(Token)中返回。監聽者需要保存這個令牌,並使用它來解除監聽。當然,監聽者完全可以忽略令牌是個什麼東西,就像地鐵票從來就是只是一張票而已,我們不曾關心它包含著什麼信息。不過對於發布者來說卻可以將一些定位信息保存在其中,從而在解除監聽時充分利用,在上面的代碼中我就保存了鏈表節點的引用,從而達到監聽者的添加、定位、移除都在常數時間內完成。
 
當然,還可以在Tocken中保存發布者的引用,這樣就可以發現”取消對一個從來沒有監聽過的對象的監聽“這樣的BUG。或者,還有其它信息。

希望本文所述對大家的C#程序設計有所幫助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved