一 前言
最近在閱讀 ASP.NET 本質論 一書詳了解了“處理管道”這一概念,處理管道是通過事件機制來實現的,事件?這個知識點對我來說只是聽過這個詞,但是很少自己全面了解過,到於用嘛,當初在asp.net 拖控件的時候用得是相當多啊。於是便全面了解了一下,其實事件應該與委托一起寫的,但是當時並不了解事件所以就不知道它倆之間的關系。
不知不覺 XX 你怎么看?系列也寫到第三篇了
1.1 事件結構
發布者:讓事件被其它類或結構可見並使用的類或結構,有點繞 簡單來說就是 聲明事件的類或者結構
訂閱者:把事件和發布者關聯注冊的類或結構,簡單說就是負責將方法注冊到事件上的類或結構
事件處理程序 :注冊到事件上的方法,就是訂閱者往事件上注冊的方法,當事件被觸發時所調用的方法就是從這里來的
用MSDN上的話說就是 發送(或引發)事件的類稱為“發行者”,接收(或處理)事件的類稱為“訂閱者”,而執行事件中的方法所在的類或結構就是 事件處理程序。
1.2 事件核心對象
EventHandler:本質上是一個委托,而且是.NET BCL 使用的預定義的用於標准事件的委托,public delegate void EventHandler(object sender, EventArgs e)
要注意這個委托是有兩個參數的,所以在聲明事件,與事件處理程序時要保持與它一樣的函數簽名。
EventArgs:表示包含事件數據的類的基類,並提供要用於不包含事件數據的事件的值,而且不能傳遞數據,如果要傳遞數據必須聲明一個從EventArgs繼承的基類
上面都是概念性的知識,一時半會不了解也沒關系,我會在下文的例子中再強化一次的,這樣會了解更深刻一點
event:聲明事件的關鍵字
二 標准事件
標准事件指得是.net Framework 定義的標准事件,
事件的調用流程大至如下圖,下圖是我根據自己的理解畫的如有不對還請指出
2.1 事件發布者
事件發布者 定義了一個事件,並提供了觸發事件的代碼,發布者定義了事件之后,訂閱者就可以去訂閱事件了
事件的聲明方法也有如下3種 單個聲明,多個聲明,以及聲明靜態事件,另外事件是成員不是類型,所以不能用new 來創建它,也只能聲明在類或結構中,事件成員被隱式自動初始化為null 也就有代碼中if (MyEvent != null)的判斷,判斷事件中是否有成員。
//事件發布者 class PublishEvent { //聲明一個事件MyEvent public event EventHandler MyEvent; //也可以一次聲明多個事件 public event EventHandler MyEvent1 , MyEvent2 , MyEvent3; //也可以是靜態的事件 public static event EventHandler StaticEvent; public System.Timers.Timer myTimer; //在調用構造函數時觸發事件 public PublishEvent() { myTimer = new System.Timers.Timer(); //Elapsed也是一個事件,我們把OnChange方法也注冊到這個事件上,所以當Elapsed事件被觸發時OnChange方法也會被觸發 myTimer.Elapsed += OnOneSecond; //設置觸發Elapsed事件的間隔時間 myTimer.Interval = 1000; //設置是否觸發Elapsed事件 myTimer.Enabled = true; } public void OnOneSecond(object source, EventArgs e) { if (MyEvent != null) { //觸發事件 MyEvent(source, e); } } }
2.2 事件訂閱者
事件訂閱者主要把方法注冊到事件上,因為我們聲明事件時是這么聲明的public event EventHandler MyEvent;
這個事件的是EventHandler委托類型的,而EventHandler本質是一個委托,所以會發現給事件注冊方法與給委托綁定方法是一樣的,而且同樣支持匿名方法,Lambda表達示
//事件訂閱者 class Program { static void Main(string[] args) { ProsessEvent prosessEvent = new ProsessEvent(); PublishEvent publish = new PublishEvent(); /*注冊事件處理程序*/ //實例方法 publish.MyEvent += prosessEvent.TimerHandler; //匿名方法 publish.MyEvent += delegate(object source, EventArgs e) { Console.Write("這是匿名處理程序\n"); }; //Lambda表達示 publish.MyEvent += (object source, EventArgs e) => Console.Write("這是Lambda處理程序\n"); Console.Read(); } }
2.3 事件處理程序
這個事件處理程序說白了那就是給事件注冊方法具體實現的地方可以是類中或者結構或者 匿名方法和Lambda表達示
//事件處理程序 class ProsessEvent { public void TimerHandler(object sorce, EventArgs e) { Console.Write("處理程序方法被調用了\n"); } }
把這三個對象分開講解后,相信對 發布者,訂閱者,事件處理程序 應該了解得更透徹一點,下面是把三個對象整合的代碼

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Timers; namespace EventDemo { //事件訂閱者 class Program { static void Main(string[] args) { ProsessEvent prosessEvent = new ProsessEvent(); PublishEvent publish = new PublishEvent(); /*注冊事件處理程序*/ //實例方法 publish.MyEvent += prosessEvent.TimerHandler; //匿名方法 publish.MyEvent += delegate(object source, EventArgs e) { Console.Write("這是匿名處理程序\n"); }; //Lambda表達示 publish.MyEvent += (object source, EventArgs e) => Console.Write("這是Lambda處理程序\n"); Console.Read(); } } //事件發布者 internal class PublishEvent { //聲明一個事件MyEvent public event EventHandler MyEvent; //也可以一次聲明多個事件 public event EventHandler MyEvent1 , MyEvent2 , MyEvent3; //也可以是靜態的事件 public static event EventHandler StaticEvent; public System.Timers.Timer myTimer; //在調用構造函數時觸發事件 public PublishEvent() { myTimer = new System.Timers.Timer(); //Elapsed也是一個事件,我們把OnChange方法也注冊到這個事件上,所以當Elapsed事件被觸發時OnChange方法也會被觸發 myTimer.Elapsed += OnOneSecond; //設置觸發Elapsed事件的間隔時間 myTimer.Interval = 1000; //設置是否觸發Elapsed事件 myTimer.Enabled = true; } public void OnOneSecond(object source, EventArgs e) { if (MyEvent != null) { //觸發事件 MyEvent(source, e); } } } //事件處理程序 class ProsessEvent { public void TimerHandler(object sorce, EventArgs e) { Console.Write("處理程序方法被調用了\n"); } } }
結果如下 因為定時器會每隔一秒觸發一次Elapsed事件所以程序會不斷執行
三 自定義事件
3.1 自定義事件類型,事件發布者
標准的事件是EventHandler委托類型,它的第二個參數是EventArgs類型,而EventArgs是不能傳遞任何數據的,現在我們需要傳遞數據,
所以要定義一個類並繼承EventArgs並定義一個字段Name用於保存數據。
那現在肯定不能再用EventHandler這個委托來做為事件的委托類型了,然后自己聲明一個與EventHandler結構一致的委托MySelfEventHandler
並把第二個參數改成我們自己定義的類MySelfEventArgs,然后重新聲明一個事件 MyEvent ,如此一來就完成了一個自定義的事件
//自定義事件類型 class MySelfEventArgs:EventArgs { public string Name; public MySelfEventArgs(string name) { Name = name; } } //事件發布者 internal class PublishEvent { /* 因為原來的事件類型EventHandler是.net 已經定義好的事件類型的委托, * 如果我們要自定義一個事件類型,則先需要定義一個事件類型的委托 */ public delegate void MySelfEventHandler(object source, MySelfEventArgs e);//原本為EventArgs //聲明一個自定義事件MyEvent public event MySelfEventHandler MyEvent; public System.Timers.Timer myTimer; //在調用構造函數時觸發事件 public PublishEvent() { myTimer = new System.Timers.Timer(); //Elapsed也是一個事件,我們把OnChange方法也注冊到這個事件上,所以當Elapsed事件被觸發時OnChange方法也會被觸發 myTimer.Elapsed += OnOneSecond; //設置觸發Elapsed事件的間隔時間 myTimer.Interval = 1000; //設置是否觸發Elapsed事件 myTimer.Enabled = true; } public void OnOneSecond(object source, EventArgs e) { if (MyEvent != null) { //因為在觸發事件時要傳入自定義類型的事件 MySelfEventArgs selfEventArgs= new MySelfEventArgs("這是自定義事件"); //觸發事件 MyEvent(source, selfEventArgs); } } }
3.2 事件訂閱者
這里還是一樣把方法注冊到事件中
//事件訂閱者 class Program { static void Main(string[] args) { ProsessEvent prosessEvent = new ProsessEvent(); PublishEvent publish = new PublishEvent(); //注冊方法 publish.MyEvent += prosessEvent.TimerHandler; Console.Read(); } }
3.3 事件處理程序
這里唯一的變化就是把原來的EventArgs改成自定義的MySelfEventArgs
//事件處理程序 class ProsessEvent { public void TimerHandler(object sorce, MySelfEventArgs e) { Console.Write("{0}\n",e.Name); } }
整合后的代碼

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Timers; namespace EventDemo { //事件訂閱者 class Program { static void Main(string[] args) { ProsessEvent prosessEvent = new ProsessEvent(); PublishEvent publish = new PublishEvent(); //注冊方法 publish.MyEvent += prosessEvent.TimerHandler; Console.Read(); } } //自定義事件類型 class MySelfEventArgs:EventArgs { public string Name; public MySelfEventArgs(string name) { Name = name; } } //事件發布者 internal class PublishEvent { /* 因為原來的事件類型EventHandler是.net 已經定義好的事件類型的委托, * 如果我們要自定義一個事件類型,則先需要定義一個事件類型的委托 */ public delegate void MySelfEventHandler(object source, MySelfEventArgs e);//原本為EventArgs //聲明一個自定義事件MyEvent public event MySelfEventHandler MyEvent; public System.Timers.Timer myTimer; //在調用構造函數時觸發事件 public PublishEvent() { myTimer = new System.Timers.Timer(); //Elapsed也是一個事件,我們把OnChange方法也注冊到這個事件上,所以當Elapsed事件被觸發時OnChange方法也會被觸發 myTimer.Elapsed += OnOneSecond; //設置觸發Elapsed事件的間隔時間 myTimer.Interval = 1000; //設置是否觸發Elapsed事件 myTimer.Enabled = true; } public void OnOneSecond(object source, EventArgs e) { if (MyEvent != null) { //因為在觸發事件時要傳入自定義類型的事件 MySelfEventArgs selfEventArgs= new MySelfEventArgs("這是自定義事件"); //觸發事件 MyEvent(source, selfEventArgs); } } } //事件處理程序 class ProsessEvent { public void TimerHandler(object sorce, MySelfEventArgs e) { Console.Write("{0}\n",e.Name); } } }
運行結果如下
四 總結
個人感覺事件相對C#的其它知識點來說,算是比較難一點的,當然我只是以我個人花的時間來判斷的,每個人情況也許不一樣,之所以花的時間多,我覺得是在於事件所涉及的三個對象 "發布者","訂閱者","事件處理程序" 之間的關系,至少我在理解這個關系時花了點時間。
把這篇文章寫完,我都不覺得我對事件了解的有多深,我覺得還只是了解了事件最基本的用法。因為每一個階段看到的東西是不一樣的,我現在的階段我想還只能看到這些。
如果您覺得本文有給您帶來一點收獲,不妨點個推薦,為我的付出支持一下,謝謝~
如果希望在技術的道路上能有更多的朋友,那就關注下我吧,讓我們一起在技術的路上奔跑