在上篇blog中簡單地介紹了委托的基礎知識,在這片文章中會介紹下委托跟 事件之間的聯系。
事件的由來
我們可以看到在使用委托進行回調 的實現的時候,我們往往需要再定義一個委托對象,以及一個對外公開的輔助方 法來添加委托的方法,這樣子會使我們感覺比較繁瑣。C#提供了event關鍵字來 減輕直接使用委托的負擔,編譯器會自動提供注冊、取消注冊的方法和委托必要 的成員。首先來看看定義事件的步驟:
1.先定義委托類型;
2.通 過event關鍵字定委托類型的事件。
public delegate int Caculate(int x, int y);
public event Caculate OnCaculate;
看看編譯器幫我們 定義了什麼
首先我們可以看到幫我們定義了一個Caculate對象,其次定義了兩個方法 add_OnCaculate跟remove_OnCaculate。我們可以在看看add_OnCaculate兩個方 法裡面的一些核心的東西。add_OnCaculate:
IL_0008: call
class [mscorlib]System.Delegate [mscorlib] System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
很明顯地看到 add_OnCaculate方法調用的就是委托的Combine方法,從而我們也可以想到 remove_OnCaculate方法調用的是Remove方法。從上面我們可以看到其實event關 鍵字只是提供給我們了一種語法上的便利措施。
一個稍微完整的例子
這個例子參考的是《C#與.NET3.0高級程序設計》上面的。使用Car來舉 例的,當車子加速時到一定限制值時會觸發一個預警事件,當超過某一個速度時 會觸發一個車子爆炸事件。首先看委托跟事件:
public delegate void CarEventHandler(string msg);
public event CarEventHandler AbortToBlow;
public event CarEventHandler Exploded;
EventCar類 中有兩個事件一個是AbortToBlow一個是Exploded。下面是Car的幾個屬性以及字 段:
private const int MaxSpeed = 180;
public int CurrSpeed { get; private set; }
public bool IsDead { get; private set; }
public string Name { get; private set; }
其中 IsDead是表示車子是否已經報廢了。下面是一個加速的方法:
public void Accelerate(int addSpeed)
{
if (IsDead)
{
if (this.Exploded!= null)
Exploded("The Car Is Dead");
}
else
{
CurrSpeed += addSpeed;
if (10 == MaxSpeed - CurrSpeed &&AbortToBlow != null)
{
AbortToBlow("Careful!Bona blow!");
}
if (CurrSpeed >= MaxSpeed)
IsDead = true;
else
Console.WriteLine("CurrentSpeed:{0}", CurrSpeed);
}
}
完整代碼:
public class EventCar
{
public delegate void CarEventHandler(string msg);
public event CarEventHandler AbortToBlow;
public event CarEventHandler Exploded;
private const int MaxSpeed = 180;
public int CurrSpeed { get; private set; }
public bool IsDead { get; private set; }
public string Name { get; private set; }
public EventCar() { }
public EventCar(string carName, int currSpeed)
{
if (currSpeed > MaxSpeed)
IsDead = true;
else
{
Name = carName;
CurrSpeed = currSpeed;
}
}
public void Accelerate(int addSpeed)
{
if (IsDead)
{
if (this.Exploded!= null)
Exploded("The Car Is Dead");
}
else
{
CurrSpeed += addSpeed;
if (10 == MaxSpeed - CurrSpeed &&AbortToBlow != null)
{
AbortToBlow("Careful!Bona blow!");
}
if (CurrSpeed >= MaxSpeed)
IsDead = true;
else
Console.WriteLine("CurrentSpeed:{0}", CurrSpeed);
}
}
}
客戶端調用:
static void Main(string [] args)
{
EventCar bmw = new EventCar("Henllyee", 110);
bmw.AbortToBlow += new EventCar.CarEventHandler (CarAboutToBlow);
bmw.Exploded += new EventCar.CarEventHandler (CarExploede);
for (var i = 0; i < 10; i++)
{
bmw.Accelerate(20);
Console.ReadLine();
}
}
public static void CarAboutToBlow(string msg)
{
Console.WriteLine(msg);
}
public static void CarExploede(string msg)
{
Console.WriteLine(msg);
}
運行效果:
規范的事件與匿名方法
我們看我們定義的事件似乎跟底層的事件有點 不一樣,底層的委托的第一個參數一般為System.Object類型的,第二個參數一 般為派生自System.EventArgs類型的。第一個參數一般表示發送事件的對象,第 二個參數表示與該事件相關的參數。我們可以定義個 CarEventArgs:
public class CarEventArgs : EventArgs
{
public readonly string msg;
public CarEventArgs(string Msg)
{
msg = Msg;
}
}
委托就可以修改成:
public delegate void CarEventHandler(object send, CarEventArgs e);
使 用時:
if (IsDead)
{
if (this.Exploded!= null)
Exploded(this,new CarEventArgs("The Car Is Dead"));
}
在上面的時候我們當監聽事件的時候都是通過定義一個唯 一的與委托簽名匹配的方法,在有的時候我們監聽的方法也許只需要處理一段簡 單的邏輯,所以每次都定義個方法畢竟比較麻煩。大都時候我們可以通過匿名方 法來監聽事件,如:
bmw.AbortToBlow += delegate(object sender, CarEventArgs e)
{
Console.WriteLine("Message:{0}",e.msg);
};
總結
這篇中分析了下事件跟委托的關系,其實事件 只是語法定義上的方便,關鍵還是理解了委托就行。