記得我在初學.NET的時候對委託和事件這對概念理解的是很模糊的,當時在看書的時候只能理解書中代碼的邏輯,知道委託和事件怎樣用代碼具體實現,但對其中的原理理解甚少。這幾天在學習ASP.NET網頁編程的時候,裡面又多次提到了事件、事件參數。於是我決定再重新理解一次委託和事件
為了形象的描述委託和事件實現的過程,我想到了“喜羊羊和灰太狼”的故事。我比較愛看動畫片,尤其是愛看像《喜羊羊與灰太狼》、《蠟筆小新》等這樣既搞笑又弱智的動畫片。沒看過《喜羊羊與灰太狼》的可以趁此機會看一下。
為了描述這個故事,我們首先需要建立兩個類:Goat和Wolf
//狼類的代碼public class Wolf { string name; //定義一個變數用於儲存狼的姓名 //建構函式 public Wolf(string name) { this.name = name; } public string Name { get { return name; } set { name = value; } } public void Scare() //狼有一個恐嚇的方法 { Console.WriteLine("哈哈,我是{0},小肥羊們快跟我回狼堡吧!",name ); Console.WriteLine(); } }//山羊類的代碼public class Goat { string name; //定義一個變數用於儲存山羊的名字 //建構函式 public Goat(string name) { this.name = name; } public string Name { get { return name; } set { name = value; } } public void Run() //羊有一個逃跑的方法 { Console.WriteLine("狼來啦,{0}快跑!" ,name); Console.WriteLine(); } } 在動畫片裡,灰太狼每次看到小羊的時候都是先和小羊聊天、說一些嚇唬小羊的話,然後再上去抓羊,這時候喜羊羊早就想出逃脫的辦法了,怪不得每次灰太狼都抓不到羊,看到羊先和他們扯淡,然後才行動。
不說廢話了,下面我給大家講怎樣用委託和事件實現:狼嚇唬小羊,小羊聽到後撒腿就跑。
首先我們要明白一個常識:灰太狼再笨也不會自己告訴小羊說我來了,你們快跑吧。所以在Wolf類當中不可以出現Goat類的對象,也就是說不可以把小羊的Run方法寫在狼的Scare方法中。Wolf和Goat雙方不能關聯,那就要通過委託來解決這個問題
委託其實就是對方法的引用,一旦為委託分配了方法,那麼委託就可以像方法一樣使用。委託定義了對方法特徵的抽象,委託和函數一樣可以有參數和傳回值,為委託分配的方法的格式要和委託一致,即方法的參數和傳回值要和委託一樣。委託可以看做是函數的“類”,為委託分配的函數就是委託的執行個體。
首先我們定義一個委託
public delegate void WolfScareEventHandler();
定義一個沒有參數和傳回值的委託,委託的名字是WolfScareEventHandler,委託用關鍵字delegate聲明,可以放在Wolf類的裡面,也可以放在類外面,因為委託本身也是一種“特殊的類”
然後我們定義一個WolfScareEventHandler委託類型的事件WolfScare
public event WolfScareEventHandler WolfScare;
事件用Event關鍵字聲明,事件要聲明在Wolf類的內部,因為這個事件是屬於Wolf類的(我們通常說類有三要素:屬性、事件、方法)
包含WolfScare事件的Wolf類代碼
public class Wolf { string name; //建構函式 public Wolf(string name) { this.name = name; } public string Name { get { return name; } set { name = value; } } //聲明一個WolfScareEventHandler委託類型的事件 public event WolfScareEventHandler WolfScare; public void Scare() { Console.WriteLine("哈哈,我是{0},小肥羊們快跟我回狼堡吧!",name ); //下面是觸發WolfScare事件的代碼 if (WolfScare != null) { WolfScare(); } } }
這樣Wolf類就具有了事件,而且在狼執行Scare方法時事件會被觸發,如果為事件分配了方法,那麼將執行方法。
最關鍵的是要看主程式的代碼:
static void Main(string[] args) { Wolf bigWolf = new Wolf("灰太狼"); //聲明一個Wolf類的對象bigWolf,名字是“灰太狼” //聲明三個Goat對象 Goat happyGoat = new Goat("喜羊羊"); Goat lazyGoat = new Goat("懶羊羊"); Goat strongGoat = new Goat("沸羊羊"); //給事件分配方法 bigWolf.WolfScare += new WolfScareEventHandler(happyGoat .Run ); bigWolf.WolfScare += new WolfScareEventHandler(lazyGoat.Run); bigWolf.WolfScare += new WolfScareEventHandler(strongGoat.Run); bigWolf.Scare (); }
在主程式當中,為bigWolf對象的事件WolfScare分配了三個方法:happyGoat .Run 、lazyGoat.Run、strongGoat.Run。用委託的好處就在這裡,不用在Wolf類當中出現Goat類,而是在主程式當中為事件動態分配方法,而且這些方法可以是不同對象的方法,只要格式和委託類型一致就可以了。
這樣在bigWolf執行Scare方法時,事件WolfScare會被觸發,同時執行happyGoat .Run 、lazyGoat.Run、strongGoat.Run三個方法,運行結果如下
有人會說.NET中控制項事件程序一般都是帶參數的,像下面這樣:
protected void Button1_Click(object sender, EventArgs e) { } 這個事件程序帶兩個參數sender和e,分別是object類型和EventArgs類型的。sender是指此事件是在哪個對象中被觸發的,在這裡就是Button1;e是一個事件參數,裡面攜帶著一些與此事件相關的資料
那麼我們對“喜羊羊和灰太狼”的代碼進行改造,讓它的事件也帶參數
先定義一個事件參數類WolfScareEventArgs,讓它繼承EventArgs類
public class WolfScareEventArgs { string name; //定義一個name變數,讓它儲存狼的名字,這樣事件參數就可以攜帶狼的名字了 public string Name { get { return name; } set { name = value; } } }對委託改造:事件程序(即為委託分配的方法)是帶參數的,那麼委託也要帶參數
public delegate void WolfScareEventHandler(object sender,WolfScareEventArgs e);
改造後的Wolf類
public class Wolf { string name; //建構函式 public Wolf(string name) { this.name = name; } public string Name { get { return name; } set { name = value; } } //聲明一個WolfScareEventHandler委託類型的事件 public event WolfScareEventHandler WolfScare; public void Scare() { Console.WriteLine("哈哈,我是{0},小肥羊們快跟我回狼堡吧!",name ); Console.WriteLine(); //下面是觸發WolfScare事件的代碼 if (WolfScare != null) { //定義一個事件參數對象 WolfScareEventArgs args = new WolfScareEventArgs(); args.Name = this.name; //將狼的名字賦給事件參數的Name屬性 WolfScare(this,args ); } //重寫ToString方法,這樣sender.tostring()就可以顯示狼的名字了 public override string ToString() { return this .name ; } }
Goat類的代碼也要進行改造:
public class Goat { string name; //建構函式 public Goat(string name) { this.name = name; } public string Name { get { return name; } set { name = value; } } public void Run(object sender,WolfScareEventArgs e) { Console.WriteLine("{0}來啦,{1}快跑!",e.Name ,name); //在這裡可以用參數e的Name屬性顯示出哪只狼來了 Console.WriteLine("追我的狼是:"+sender.ToString ()); //這裡可以用sender對象的ToString方法顯示出觸發事件的狼的名字 Console.WriteLine(); } }主程式基本不用變,為了方便示範,我們把狼的名字改為“紅太狼”
static void Main(string[] args) { Wolf bigWolf = new Wolf("紅太狼"); Goat happyGoat = new Goat("喜羊羊"); Goat lazyGoat = new Goat("懶羊羊"); Goat strongGoat = new Goat("沸羊羊"); //給事件添加方法 bigWolf.WolfScare += new WolfScareEventHandler(happyGoat .Run ); bigWolf.WolfScare += new WolfScareEventHandler(lazyGoat.Run); bigWolf.WolfScare += new WolfScareEventHandler(strongGoat.Run); bigWolf.Scare (); }
程式啟動並執行效果:
講到這裡可能有很多人還是覺得上面講的事件和.NET中的控制項事件不太一樣,其實他們的原理是一樣的:
當我們雙擊某個控制項時,代碼編輯器會自動為我們編寫好一個事件處理過程
protected void Button1_Click(object sender, EventArgs e) { } 其實這裡的Button1_Click 就類似於上面講到的Goat類的Run方法。Button1_Click過程的背後是事件Button1.Click,它類似於上面的bigWolf.WolfScare事件。 其實Button1_Click可以是隨便的一個名字,比如 fun (object sender, EventArgs e),只要你在代碼中添加如下語句:
Button1.Click+=New EventHandler(fun);
這樣當Button1的Click事件被觸發時,就會執行fun方法。你可以為Button1.Click事件分配任意多的方法,當事件被觸發時,這些方法都會被執行。
你看到的事件程序都是類似於 protected void Button1_Click(object sender, EventArgs e)這樣的,這是因為:當你雙擊某個控制項時,系統自動建立過程protected void Button1_Click(object sender, EventArgs e),並將它分配給事件Button1.Click. 你也可以把其它控制項的事件程序分配給事件Button1.Click,如:
protected void TextBox1_TextChanged(object sender, EventArgs e) { Response.Write("這是文字框的事件程序"); }Button1.Click+=New EventHandler(TextBox1_TextChanged) 這樣單擊Button1,TextBox1_TextChanged事件程序也會被執行。 總之事件程序和一般的函數和方法是一樣的,只不過系統把它分配給了事件,當事件觸發時,執行被分配的事件程序
事件的原理你明白了嗎?