在所有關於C#事件機制的介紹中,我更傾向於發佈者/訂閱者(Publisher/Subscriber)這種描述。理解事件機制並不是一件容易的事情,它所涉及的思想值得我們好好去研究。
本文資源來自《C#與.NET技術平臺實戰演練》——中國青年出版社
談到事件,我們涉及到兩個角色:事件發佈者(Publisher)和事件訂閱者(Scriber),也可以說是事件發送者(Sender)和事件接收者(Receiver)的關係。舉個例子來說,市面上目前有許多雜誌,雜誌的種類也很多。而我只對其中的某些感興趣,那麼我就可以向雜誌發行商提出訂閱。之後,每當雜誌發行時,我就會收到我在雜誌發行商那兒訂閱的雜誌。在這個關係中,雜誌發行商就相當於事件發行者,而我就是事件訂閱者。每當雜誌發行時,就觸發了一個發行事件。
用面向對象的語言解釋,這兩者的意義如下:
事件發行者(Publisher)
它是一個對象,且會維護自身的狀態信息。每當狀態信息發生變動時,便觸發一個事件,並通知所有的事件訂閱者。對於雜誌發行商來說,每本雜誌都有自己的信息在裏面,當雜誌發行時,我要通知訂閱該雜誌的人:雜誌已經發行啦,請注意查收!
事件接收者(Receiver)
這個對象要註冊它感興趣的對象,也就是訂閱它自己喜歡的雜誌啦。另外,這個對象通常要提供一個事件處理方法,在事件發行者觸發一個事件後,會自動執行這個方法。對於上面所舉的例子來說,也就是我收到雜誌後要做什麼事情,比如,你可以滿世界地大喊:我收到雜誌啦!也可以將雜誌收藏起來慢慢欣賞,具體怎麼實現完全取決你自己的喜好。
以下是.NET事件處理機制的模型:
下面給一個簡單的例子,用以闡述事件的思想:
1using System;
2using System.Collections.Generic;
3using System.Text;
4
5namespace EventDemo
6{
7 public delegate void SalaryCompute(); //聲明一個代理類
8
9 public class Employee
10 {
11 public event SalaryCompute OnSalaryCompute; //定義事件,將其與代理綁定
12
13 public virtual void FireEvent() //觸發事件的方法
14 {
15 if (OnSalaryCompute != null)
16 {
17 OnSalaryCompute(); //觸發事件
18 }
19 }
20 }
21
22 public class HumanResource
23 {
24 public void SalaryHandler() //事件處理函數
25 {
26 Console.WriteLine("Salary"); //只是打印一行字而已
27 }
28
29 public static void Main()
30 {
31 Employee ep = new Employee();
32 HumanResource hr = new HumanResource();
33 ep.OnSalaryCompute+=new SalaryCompute(hr.SalaryHandler); //註冊
34 ep.FireEvent(); //觸發事件
35 Console.Read();
36 }
37 }
38}
39在這個例子中,Employee類相當於一個事件發佈者(Publisher),它定義了事件的相關信息,包括定義了一個事件用於計算薪水(OnSalaryCompute),以及一個觸發事件的函數(FireEvent()),爲簡單起見,本例沒有加上事件參數。
與之相對應,HumanResource類則相當於一個事件訂閱者(Subscriber),它定義了一個事件處理函數(SalaryHandler()),並用+=將其與事件聯繫起來,從而使事件觸發的時候能夠調用我這個方法(在本例中也就是打印一行字啦)。值得注意的一點是,事件處理函數的方法簽名要與代理的方法簽名相同,這是非常重要的一點。
下面將這個例子改造一下,事件參數信息,用以完善事件機制。
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading;
5
6namespace EventDemo
7{
8 public delegate void SalaryCompute(object sender,MyEventArgs e); //聲明一個代理類
9
10 public class Employee
11 {
12 public event SalaryCompute OnSalaryCompute; //定義事件,將其與代理綁定
13
14 public virtual void FireEvent(MyEventArgs e) //觸發事件的方法
15 {
16 if (OnSalaryCompute != null)
17 {
18 OnSalaryCompute(this,e); //觸發事件
19 }
20 }
21 }
22
23 public class MyEventArgs : EventArgs //定義事件參數類
24 {
25 public readonly double _salary;
26 public MyEventArgs(double salary)
27 {
28 this._salary = salary;
29 }
30 }
31
32 public class HumanResource
33 {
34 public void SalaryHandler(object sender,MyEventArgs e) //事件處理函數,其簽名應與代理簽名相同
35 {
36 Console.WriteLine("Salary is {0}",e._salary); //只是打印一行字而已
37 }
38
39 public static void Main()
40 {
41 Employee ep = new Employee();
42 HumanResource hr = new HumanResource();
43 MyEventArgs e = new MyEventArgs(123.40);
44 ep.OnSalaryCompute+=new SalaryCompute(hr.SalaryHandler); //註冊
45 for (; ; )
46 {
47 Thread.Sleep(1000); //讓程序“睡”一秒
48 ep.FireEvent(e); //觸發事件
49 }
50 //Console.Read();
51 }
52 }
53}
54
這個例子很有意思,它一秒鐘自動觸發事件一次,比上一個例子更能解釋事件的機制,對吧?在這個例子中,我們要注意的一個地方就是事件處理函數的簽名要和代理的簽名一致