在前面一篇中寫到了委托,也說了委托是C#中很多特性的基礎,這篇要講的事件,就是建立在委托之上的。在C#1.0中,委托和事件是最重要的兩個特性。
1、什麼是事件?
事件設計到兩類角色——事件發布者和事件訂閱者。當某個事件發生後,事件發布者會發布消息;事件訂閱者會接收到信息,並做出相應的處理,這就是事件的過程。
2、使用事件
2.1 定義事件
在C#中定義事件和定義類的成員是很相似的,只要一個event關鍵字就可以了。比如:
public event EventHandler birthday;
其中event是關鍵字,而EventHandler是委托類型。
所以可以把事件定義的結構總結為:訪問修飾符 event 委托類型 事件名;其中委托類型可以是自定義的委托類型,也可以是.NET類庫中預定義的委托類型EventHandler。
2.2 訂閱和取消事件
事件訂閱者需要訂閱事件發布者發布的事件消息,以便在事件被觸發式接收消息並做出相應處理。在C#中,可以使用“+=”來訂閱事件,使用“-=”來取消訂閱事件。
public class Bridegroom
{
//自定義委托
public delegate void MarryHandler(string msg);
//使用自定義委托類型定義事件,事件名為MarryEvent
public event MarryHandler MarryEvent;
//發出事件
public void OnMarriageComing(string msg)
{
//判斷是否綁定了事件處理方法
if(MarryEvent!=null)
{
//觸發事件
MarryEvent(msg);
}
}
static void Main(string[] msg)
{
Bridegroom bridegroom=new Bridegroom();
//實例化朋友對象
Friend friend1=new Friend("張三");
Friend friend2=new Friend("李四");
Friend friend3=new Friend("王五");
//使用“+=”來訂閱事件
bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);
//發出通知,此時只有訂閱了事件的對象才能收到通知
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.WriteLine("------------------------------------");
//使用"-="來取消事件訂閱,此時李四將收不到通知
bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);
bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.ReadKey();
}
}
public class Friend
{
public string Name;
public Friend(string name)
{
Name=name;
}
//事件處理函數,該函數需要符合MarryHandler委托的定義
public void SendMessage(string message)
{
Console.WriteLine(message);
Console.WriteLine(this.Name+"收到了,到時候准時參加");
}
}
值得注意的是,事件處理函數的定義需要與自定義的委托定義保持一致,即參數個數,參數類型和返回類型等需要與委托相同。
除了使用自定義委托類型來定義事件外,還可以使用.NET類庫中預定義的委托類型EventHandler來定義事件,需要注意它們的參數。
public class Bridegroom
{
//使用.NET類庫中的類型定義事件,事件名為MarryEvent
public event EventHandler MarryEvent;
//發出事件
public void OnMarriageComing(string msg)
{
//判斷是否綁定了事件處理方法
if(MarryEvent!=null)
{
Console.WriteLine(msg);
//觸發事件
MarryEvent(this,new EventArgs());
}
}
static void Main(string[] msg)
{
Bridegroom bridegroom=new Bridegroom();
//實例化朋友對象
Friend friend1=new Friend("張三");
Friend friend2=new Friend("李四");
Friend friend3=new Friend("王五");
//使用“+=”來訂閱事件
bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);
//發出通知,此時只有訂閱了事件的對象才能收到通知
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.WriteLine("------------------------------------");
//使用"-="來取消事件訂閱,此時李四將收不到通知
bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);
bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.ReadKey();
}
}
public class Friend
{
public string Name;
public Friend(string name)
{
Name=name;
}
//事件處理函數,該函數需要符合MarryHandler委托的定義
public void SendMessage(object s,EventArgs e)
{
Console.WriteLine(this.Name+"收到了,到時候准時參加");
}
}
EventHandler是.NET類庫中預定義的委托類型,用於處理不包含事件數據的事件。使用Reflector來查看EventHandler的具體定義:
[Serializable, ComVisible(true), __DynamicallyInvokable] public delegate void EventHandler(object sender, EventArgs e);
從定義中可以看出,該委托類型的返回類型為void,第一個參數sender負責保存觸發事件對象的引用,其類型為object;第二個參數e負責保存事件數據。EventArgs類也是.NET類庫中定義的類,它不保存任何數據,如果想在事件中包含事件數據,就必須使用EventArgs的派生類來實現。
2.3 擴展EventArgs類
上面說了,如果要在事件中包含事件數據,就必須使用EventArgs的派生類。具體的實現代碼如下:
public class MarryEventArgs:EventArgs
{
public string Message;
public MarryEventArgs(string msg)
{
Message=msg;
}
}
public class Bridegroom
{
//自定義委托類型,委托包含兩個參數
public delegate void MarryHandler(object sender,MarryEventArgs e);
//使用自定義委托類型定義事件,事件名為MarryEvent
public event MarryHandler MarryEvent;
//發出事件
public void OnMarriageComing(string msg)
{
//判斷是否綁定了事件處理方法
if(MarryEvent!=null)
{
//觸發事件
MarryEvent(this,new MarryEventArgs(msg));
}
}
static void Main(string[] msg)
{
Bridegroom bridegroom=new Bridegroom();
//實例化朋友對象
Friend friend1=new Friend("張三");
Friend friend2=new Friend("李四");
Friend friend3=new Friend("王五");
//使用“+=”來訂閱事件
bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);
//發出通知,此時只有訂閱了事件的對象才能收到通知
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.WriteLine("------------------------------------");
//使用"-="來取消事件訂閱,此時李四將收不到通知
bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);
bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.ReadKey();
}
}
public class Friend
{
public string Name;
public Friend(string name)
{
Name=name;
}
//事件處理函數,該函數需要符合MarryHandler委托的定義
public void SendMessage(object s,MarryEventArgs e)
{
Console.WriteLine(e.Message);
Console.WriteLine(this.Name+"收到了,到時候准時參加");
}
}
通過自定義MarryEventArgs事件類擴展了EventArgs類,此時MarryEventArgs帶有一個名為Message的事件參數;然後在訂閱對象的SendMessage方法中,通過e.Message的方式獲得了事件數據,並把事件數據輸出。
3、事件的本質
從以上的例子我們可以知道,事件是在委托的基礎之上的。那麼,它們到底有著什麼樣的關系呢,這個就必須通過Reflector來窺探了。
簡單的源代碼:
namespace 窺探事件本質
{
class Program
{
public delegate void MarryHanler(string msg);
public event MarryHanler MarryEvent;
static void Main(string[] args)
{
}
}
}
Reflector反編譯的結果:
圖1
圖2
圖3
圖4
可以看出,C#事件被編譯成包含兩個公共方法的代碼段,一個帶有add_前綴,另一個帶有remove_前綴,前綴後面是C#事件的名稱。
在add_方法中,通過調用了Delegate.Combine()方法來實現的(圖3中紅框的地方),Delegate.Combine()方法將多個委托組合為了一個多路廣播委托。
在remove_方法中,同樣采用了Delegate.Remove()方法。
由上面的四張圖中可以總結出:
C#的事件是一個特殊的多路廣播委托,事件默認含有一個私有的委托類型變量(圖2的紅框),該變量用於保存對事件處理方法的引用,且該委托類型的變量為私有,只能從定義該事件的類中進行訪問。
從反編譯的代碼中可以看出跟我們學過的屬性是相似的。但與事件不同,屬性中定義了set訪問和get訪問器,兩個訪問器的本質就是以"get_"和"set_"為前綴的兩個方法。屬性用於對類中的私有字段進行訪問,而C#事件也可以看作是“委托字段的屬性”,因此可以通過事件來對私有的委托字段進行訪問,這也是C#事件特性存在的原因。C#事件機制符合面向對象的封裝特性,是代碼更安全。