.NET 事件模型教程(二)

weixin_34119545發表於2019-01-08
[日期:2005-01-22] 來源:部落格堂  作者:破寶 [字型: ]

目錄

屬性樣式的事件宣告

在第一節中,我們討論了 .NET 事件模型的基本實現方式。這一部分我們將學習 C# 語言提供的高階實現方式:使用 add/remove 訪問器宣告事件。(注:本節內容不適用於 VB.NET。)

我們再來看看上一節中我們宣告事件的格式:

        public event [委託型別] [事件名稱];

這種宣告方法,類似於類中的欄位(field)。無論是否有事件處理程式掛接,它都會佔用一定的記憶體空間。一般情況中,這樣的記憶體消耗或許是微不足 道的;然而,還是有些時候,記憶體開銷會變得不可接受。比如,類似 System.Windows.Forms.Control 型別具有五六十個事件,這些事件並非每次都會掛接事件處理程式,如果每次都無端的多處這麼多的記憶體開銷,可能就無法容忍了。

好在 C# 語言提供了“屬性”樣式的事件宣告方式:

        public event [委託型別] [事件名稱]
        {
            add { .... }
            remove { .... }
        }

如上的格式宣告事件,具有 add 和 remove 訪問器,看起來就像屬性宣告中的 get 和 set 訪問器。使用特定的儲存方式(比如使用 Hashtable 等集合結構),通過 add 和 remove 訪問器,自定義你自己的事件處理程式新增和移除的實現方法。

Demo 1G:“屬性”樣式的事件宣告。我首先給出一種實現方案如下(此實現參考了 .NET Framework SDK 文件中的一些提示)(限於篇幅,我只將主要的部分貼在這裡):

        public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
        public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);

        // 注意:本例中的實現,僅支援“單播事件”。
        // 如需要“多播事件”支援,請參考 Demo 1H 的實現。

        // 為每種事件生成一個唯一的 object 作為鍵
        static readonly object StartWorkEventKey = new object();
        static readonly object EndWorkEventKey = new object();
        static readonly object RateReportEventKey = new object();

        // 使用 Hashtable 儲存事件處理程式
        private Hashtable handlers = new Hashtable();

        // 使用 protected 方法而沒有直接將 handlers.Add / handlers.Remove
        // 寫入事件 add / remove 訪問器,是因為:
        // 如果 Worker 具有子類的話,
        // 我們不希望子類可以直接訪問、修改 handlers 這個 Hashtable。
        // 並且,子類如果有其他的事件定義,
        // 也可以使用基類的這幾個方法方便的增減事件處理程式。
        protected void AddEventHandler(object eventKey, Delegate handler)
        {
            lock(this)
            {
                if (handlers[ eventKey ] == null)
                {
                    handlers.Add( eventKey, handler );
                }
                else
                {
                    handlers[ eventKey ] = handler;
                }
            }
        }

        protected void RemoveEventHandler(object eventKey)
        {
            lock(this)
            {
                handlers.Remove( eventKey );
            }
        }

        protected Delegate GetEventHandler(object eventKey)
        {
            return (Delegate) handlers[ eventKey ];
        }

        // 使用了 add 和 remove 訪問器的事件宣告
        public event StartWorkEventHandler StartWork
        {
            add { AddEventHandler(StartWorkEventKey, value); }
            remove { RemoveEventHandler(StartWorkEventKey); }
        }

        public event EventHandler EndWork
        {
            add { AddEventHandler(EndWorkEventKey, value); }
            remove { RemoveEventHandler(EndWorkEventKey); }
        }
        
        public event RateReportEventHandler RateReport
        {
            add { AddEventHandler(RateReportEventKey, value); }
            remove { RemoveEventHandler(RateReportEventKey); }
        }

        // 此處需要做些相應調整
        protected virtual void OnStartWork( StartWorkEventArgs e )
        {
            StartWorkEventHandler handler = 
                (StartWorkEventHandler) GetEventHandler( StartWorkEventKey );
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnEndWork( EventArgs e )
        {
            EventHandler handler =
                (EventHandler) GetEventHandler( EndWorkEventKey );

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRateReport( RateReportEventArgs e )
        {
            RateReportEventHandler handler =
                (RateReportEventHandler) GetEventHandler( RateReportEventKey );

            if (handler != null)
            {
                handler(this, e);
            }
        }

        public Worker()
        {
        }

        public void DoLongTimeTask()
        {
            int i;
            bool t = false;
            double rate;

            OnStartWork(new StartWorkEventArgs(MAX) );

            for (i = 0; i <= MAX; i++)
            {
                Thread.Sleep(1);
                t = !t;
                rate = (double)i / (double)MAX;

                OnRateReport( new RateReportEventArgs(rate) );
            }

            OnEndWork( EventArgs.Empty );
        }

細細研讀這段程式碼,不難理解它的演算法。這裡,使用了名為 handlers 的 Hashtable 儲存外部掛接上的事件處理程式。每當事件處理程式被“add”,就把它加入到 handlers 裡儲存;相反 remove 時,就將它從 handlers 裡移除。這裡取 event 的 key (開始部分為每一種 event 都生成了一個 object 作為代表這種 event 的 key)作為 Hashtable 的鍵。

[TOP]

 

單播事件和多播事件

在 Demo 1G 給出的解決方案中,你或許已經注意到:如果某一事件被掛接多次,則後掛接的事件處理程式,將改寫先掛接的事件處理程式。這裡就涉及到一個概念,叫“單播事件”。

所謂單播事件,就是物件(類)發出的事件通知,只能被外界的某一個事件處理程式處理,而不能被多個事件處理程式處理。也就是說,此事件只能被掛接一次,它只能“傳播”到一個地方。相對的,就有“多播事件”,物件(類)發出的事件通知,可以同時被外界不同的事件處理程式處理。

打個比方,上一節開頭時張三大叫一聲之後,既招來了救護車,也招來了警察叔叔(問他是不是回不了家了),或許還有電視轉播車(現場直播、採訪張三為什麼大叫,呵呵)。

多播事件會有很多特殊的用法。如果以後有機會向大家介紹 Observer 模式,可以看看 Observer 模式中是怎麼運用多播事件的。(注:經我初步測試,欄位形式的事件宣告,預設是支援“多播事件”的。所以如果在事件種類不多時,我建議你採用上一節中所講 的欄位形式的宣告方式。)

[TOP]

 

支援多播事件的改進

Demo1H,支援多播事件。為了支援多播事件,我們需要改進儲存結構,請參考下面的演算法:

        public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
        public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);

        // 為每種事件生成一個唯一的鍵
        static readonly object StartWorkEventKey = new object();
        static readonly object EndWorkEventKey = new object();
        static readonly object RateReportEventKey = new object();

        // 為外部掛接的每一個事件處理程式,生成一個唯一的鍵
        private object EventHandlerKey
        {
            get { return new object(); }
        }

        // 對比 Demo 1G,
        // 為了支援“多播”,
        // 這裡使用兩個 Hashtable:一個記錄 handlers,
        // 另一個記錄這些 handler 分別對應的 event 型別(event 的型別用各自不同的 eventKey 來表示)。
        // 兩個 Hashtable 都使用 handlerKey 作為鍵。

        // 使用 Hashtable 儲存事件處理程式
        private Hashtable handlers = new Hashtable();
        // 另一個 Hashtable 儲存這些 handler 對應的事件型別
        private Hashtable events = new Hashtable();

        protected void AddEventHandler(object eventKey, Delegate handler)
        {
            // 注意新增時,首先取了一個 object 作為 handler 的 key,
            // 並分別作為兩個 Hashtable 的鍵。

            lock(this)
            {
                object handlerKey = EventHandlerKey;
                handlers.Add( handlerKey, handler );
                events.Add( handlerKey, eventKey);
            }
        }

        protected void RemoveEventHandler(object eventKey, Delegate handler)
        {
            // 移除時,遍歷 events,對每一個符合 eventKey 的項,
            // 分別檢查其在 handlers 中的對應項,
            // 如果兩者都吻合,同時移除 events 和 handlers 中的對應項。
            //
            // 或許還有更簡單的演算法,不過我一時想不出來了 :(

            lock(this)
            {
                foreach ( object handlerKey in events.Keys)
                {
                    if (events[ handlerKey ] == eventKey)
                    {
                        if ( (Delegate)handlers[ handlerKey ] == handler )
                        {
                            handlers.Remove( handlers[ handlerKey ] );
                            events.Remove( events[ handlerKey ] );
                            break;
                        }
                    }
                }
            }
        }

        protected ArrayList GetEventHandlers(object eventKey)
        {
            ArrayList t = new ArrayList();

            lock(this)
            {
                foreach ( object handlerKey in events.Keys )
                {
                    if ( events[ handlerKey ] == eventKey)
                    {
                        t.Add( handlers[ handlerKey ] );
                    }
                }
            }

            return t;
        }

        // 使用了 add 和 remove 訪問器的事件宣告
        public event StartWorkEventHandler StartWork
        {
            add { AddEventHandler(StartWorkEventKey, value); }
            remove { RemoveEventHandler(StartWorkEventKey, value); }
        }

        public event EventHandler EndWork
        {
            add { AddEventHandler(EndWorkEventKey, value); }
            remove { RemoveEventHandler(EndWorkEventKey, value); }
        }
        
        public event RateReportEventHandler RateReport
        {
            add { AddEventHandler(RateReportEventKey, value); }
            remove { RemoveEventHandler(RateReportEventKey, value); }
        }

        // 此處需要做些相應調整
        protected virtual void OnStartWork( StartWorkEventArgs e )
        {
            ArrayList handlers = GetEventHandlers( StartWorkEventKey );

            foreach(StartWorkEventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

        protected virtual void OnEndWork( EventArgs e )
        {
            ArrayList handlers = GetEventHandlers( EndWorkEventKey );

            foreach(EventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRateReport( RateReportEventArgs e )
        {
            ArrayList handlers = GetEventHandlers( RateReportEventKey );

            foreach(RateReportEventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

上面給出的演算法,只是給你做參考,應該還有比這個實現更簡單、更高效的方式。

為了實現“多播事件”,這次使用了兩個 Hashtable:一個儲存“handlerKey - handler”對,一個儲存“handlerKey - eventKey”對。相信通過仔細研讀,你可以讀懂這段程式碼。我就不再贅述了。

引用自 : http://www.iwms.net/n1358c13.aspx

相關文章