標籤:style blog http io ar color os 使用 sp
事件:
"在發生其他類或對象關注的事情時,類或對象可以通過事件通知他們。發送(或引發)事件的類稱為"發行者",接受(或處理)事件的類稱為"訂戶"。"
上面這句話描述了事件的最本質功能,用於底層通知上層。正常的架構設計都是分層結構,而分層結構有一點很重要的就是底層對於上層的無知,當初這樣設計是為瞭解耦,為了更好的物件導向,但是帶來的問題是如何解決自下而上的資訊流。因為自上而下的調用,我們通過介面就可以搞定一切了,上層可以看到下層提供的服務介面,那麼正常的調用可以保證一路向下,底層調用中層提供的服務介面,中層的服務介面的實現中調用了底層的服務介面,這樣感覺很是完美的設計模式。每一層都不再依賴彼此,隱藏了實現細節。但是現在遇到一個最簡單的問題:如果需要底層來觸發上層的行為,如何?。很多程式員告訴我這個簡單,輪詢啊,底層不斷輪詢這一個事情的發生狀況,如果發生了則啟動一個線程專門去處理這個事情。這種解決方案只需要在底層多開出一個服務介面,該服務介面就是表示目前發生了什麼事情,然後上層定時查看該介面,如果發生則採取相應操作。當然該種解決方案也是一種解決途徑,但是估計你也覺得不好,第一無法即時,因為輪詢,那麼必定存在一個時差問題,也就是常說的回應時間問題。還有就是單獨的輪詢線程需要空間與時間的消耗。最讓人鬱悶還在於這個對於時空的消耗竟然與回應時間是反相關的,總之你想回應時間短,那麼就意味著你不得不浪費大量時空,反之亦然。當然此種方法還要解決多線程衝突的問題,涉及到多線程衝突,鎖解鎖的問題,那麼我覺得就不怕你的邏輯能力有多強,耐心有多大,隨著項目規模的變大,線程的變多,你大腦崩潰那是早晚的事情。 此處我們引入事件模式。
先來看看事件的特徵: ?發行者確定何時引發事件,訂戶確定執行何種操作來響應該事件 ?一個事件可以有多個訂戶。一個訂戶可處理來自多個發行者的多個事件 ?沒有訂戶的事件永遠不會被調用 ?事件通常用於通知使用者操作 ?如果一個事件有多個訂戶,當引發該事件時,會同步調用多個事件處理常式 ?支援非同步呼叫 ?可以利用事件同步線程 ?在 .NET Framework 類庫中,事件是基於 EventHandler 委託和 EventArgs 基類的
C#類庫中內建了一大堆事件,尤其那些控制項。而對於我說到的這個底層觸發上層的問題,那麼絕大多數是需要自訂事件的。(庫中內建事件的使用我就不講了,如果這個你不會的話,未免對不起觀眾了。)所以下面就開始著重講講自訂事件的問題:
事件是類和對象向外界發出的訊息,事件的執行是通過事件委託的方式,調用我們所準備好的處理方法。要響應某些事件並針對某些事件執行我們指定的方法,需要做到以下幾步:
?
///定義一個委託 public delegate void TestEventHandler( object sender, TestEventArgs e); ///用event關鍵字聲明事件對象 public event TestEventHandler TestEvent; |
?
//事件觸發的方法 protected void OnTestEvent(TestEventArgs e) { if (TestEvent != null ) { TestEvent( this , e); } } |
?
//引發事件的方法 public void RaiseEvent( char keyToRaiseEvent) { TestEventArgs e = new TestEventArgs(keyToRaiseEvent); OnTestEvent(e); } |
?
//定義本地處理事件的方法,他與聲明事件的delegate具有相同的參數和傳回值類型 public void KeyPressed( object sender, TestEventSource.TestEventArgs e) { Console.WriteLine( "寄件者:{0},所按得健為:{1}" , sender, e.KeyToRaiseEvent); } |
?
//訂閱事件 public void Subscribe(TestEventSource evenSource) { evenSource.TestEvent += new TestEventSource.TestEventHandler(KeyPressed); } //取消訂閱事件 public void UnSubscribe(TestEventSource evenSource) { evenSource.TestEvent -= new TestEventSource.TestEventHandler(KeyPressed); } |
最終全部代碼如下:
View Code
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication1{ /// <summary> /// 發布事件類別 /// </summary> public class TestEventSource { /// <summary> /// 定義事件參數類 /// </summary> public class TestEventArgs : EventArgs { public readonly char KeyToRaiseEvent; public TestEventArgs(char keyToRaiseEvent) { KeyToRaiseEvent = keyToRaiseEvent; } } ///定義一個委託 public delegate void TestEventHandler(object sender, TestEventArgs e); ///用event關鍵字聲明事件對象 public event TestEventHandler TestEvent; //事件觸發的方法 protected void OnTestEvent(TestEventArgs e) { if (TestEvent != null) { TestEvent(this, e); } } //引發事件的方法 public void RaiseEvent(char keyToRaiseEvent) { TestEventArgs e = new TestEventArgs(keyToRaiseEvent); OnTestEvent(e); } } //監聽事件類別 public class TestEventListener { //定義本地處理事件的方法,他與聲明事件的delegate具有相同的參數和傳回值類型 public void KeyPressed(object sender, TestEventSource.TestEventArgs e) { Console.WriteLine("寄件者:{0},所按得健為:{1}", sender, e.KeyToRaiseEvent); } //訂閱事件 public void Subscribe(TestEventSource evenSource) { evenSource.TestEvent += new TestEventSource.TestEventHandler(KeyPressed); } //取消訂閱事件 public void UnSubscribe(TestEventSource evenSource) { evenSource.TestEvent -= new TestEventSource.TestEventHandler(KeyPressed); } } class Program { static void Main(string[] args) { ///建立事件來源對象 TestEventSource es = new TestEventSource(); ///建立監聽對象 TestEventListener el = new TestEventListener(); ///訂閱事件 Console.WriteLine("訂閱事件\t"); el.Subscribe(es); ///引發事件 Console.WriteLine("輸入一個字元,再按enter鍵"); string str = Console.ReadLine(); es.RaiseEvent(str.ToCharArray()[0]); //取消訂閱事件 Console.WriteLine("\n取消訂閱事件\n"); el.UnSubscribe(es); //引發事件 Console.WriteLine("輸入一個字元,再按enter健"); str = Console.ReadLine(); es.RaiseEvent(str.ToCharArray()[0]); Console.ReadLine(); } }}
賦值粘貼即可以執行,且看下面執行效果:
C#委託五(自訂事件)