庖丁解牛——深入解析委託和事件
這篇博文我不講委託和事件的概念,因為大段的文字概念沒有任何意義。 具體想了解,委託和事件的概念可以MSDN查閱。 我這篇文章的主題思路是委託如何一步步進化成事件: 何為委託--->委託來實現事件--->用方法對委託的封裝--->Event的 add,remove方法的引入--->標準事件寫法--->反編譯探究竟。 用幾個例子以及Reflector反編譯探究委託和事件的關係。不足之處,還望多多指教...
何為委託:
首先,委託是一種型別,是一種定義了方法簽名的型別。
委託可以理解為函式指標(安全),並且委託約束了方法的簽名(由返回型別和引數組成),
所以例項化委託時,可以將其例項與任何具有相同簽名(由返回型別和引數組成)得方法相關聯,
如果按照C語言函式指標理解,即委託例項指向某個方法。
為什麼要用委託:
舉個簡單的例子:
例如,需要判斷一個數是為奇數還是偶數? 可能我們會這樣實現:
static void Main(string[] args)
{
Console.WriteLine((4 % 2 == 0) ? "偶數" : "奇數");
Console.WriteLine((5 % 2 == 0) ? "偶數" : "奇數");
Console.ReadKey();
}
}
上面例子很簡單,但是很不靈活,我們稍加改進:
static void Main(string[] args)
{
Console.WriteLine("請輸入一個數字:");
int i = int.Parse(Console.ReadLine());
Console.WriteLine((i%2==0)?"偶數":"奇數");
Console.ReadKey();
}
上面這個簡單的例子,也是挺有玄機的。對於程式設計師,我們不關心客戶端使用者傳過來是奇數還是偶數,況且我們也不知道傳過來的引數到底是多少, 我們只關心怎樣來實現功能。對於使用者來說,他們不必關心底層到底是怎樣實現功能的,他們只負責輸入數字即可“坐享其成”。這個例子, 可以理解成一個最簡單的解耦。
看了上面這個例子,我們再舉一個例子來演示委託怎麼替做什麼:
//委託是一種定義方法簽名的型別。 當例項化委託時,可以將其例項與任何具有相容簽名的方法相關聯。 可以通過委託例項呼叫方法。
//一個委託宣告:
public delegate void ChangeDelegate(int i);
class Program
{
static void Main(string[] args)
{
ChangeDelegate c = null;
if (DateTime.Now.Second%2==0)
{
c = Even;//委託可以理解為函式指標(安全),並且委託型別ChangeDelegate約束了引數型別,所以c可以指向Even方法
c(DateTime.Now.Second);
}
else
{
//c = Odd;
c = new ChangeDelegate(Odd);//標準寫法
c(DateTime.Now.Second);
}
Console.ReadKey();
}
static void Even(int i)
{
Console.WriteLine("{0}是偶數",i);
}
static void Odd(int i)
{
Console.WriteLine("{0}是奇數",i);
}
}
上面程式碼可以看出,程式設計師並不知道委託例項到底指向那個函式,但他可以確定,指向的那個方法必定是受ChangeDelegate約束的。 再看一個例子,我們要對陣列進行操作:
class Program
{
static void Main(string[] args)
{
int[] arr = { 1,2,3,4,5,6,7,8,9};
List<int> newList=Filter(arr,IsOdd);
Console.WriteLine(string.Join("-",newList.ToArray()));
Console.ReadKey();
}
/// <summary>
/// 判斷是否為偶數
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
static bool IsEven(int i)
{
return i%2==0;
}
/// <summary>
/// 判斷是否為奇數
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
static bool IsOdd(int i)
{
return i%2==1;
}
static List<int> Filter(int[] value, FilterDelegate f)
{
List<int> list=new List<int> ();
foreach (var item in value)
{
if (f(item))
{
list.Add(item);
}
}
return list;
}
}
delegate bool FilterDelegate(int i);
所以,static List<int> Filter(int[] value, FilterDelegate f),這兒只需要傳過來一個方法, 而我們程式設計師並不關心這是個什麼方法,能替我們做什麼,這就是委託的好處。
事件和委託的聯絡:學習事件之前,先來用委託來模擬實現事件:
class Program
{
static void Main(string[] args)
{
Counter c = new Counter();
c.onCount = Count;//相當於訂閱了一個事件
c.onCount += Count_2;//可以多個人同時監聽了
int j = 0;
while (j<=100)
{
j++;
c.Next();
}
Console.ReadKey();
}
static void Count(int value)
{
Console.WriteLine("Count監聽了{0}是偶數", value);
}
static void Count_2(int value)
{
Console.WriteLine("Count_2監聽了{0}是偶數", value);
}
}
class Counter
{
public OnCountDelegate onCount;//把委託物件宣告為一個欄位
private int i = 0;
public void Next()
{
i++;
if (i%2==0)
{
if (onCount!=null)
{
//解耦:解除耦合。
//不用關心到底指向誰,呼叫就行
onCount(i);//觸發事件,呼叫onCount指向的函式,相當於把事件通知出去
}
}
}
}
public delegate void OnCountDelegate(int value);
c.onCount = Count;
c.onCount += Count_2; 相當於監聽事件,觸發事件時,只要哦呼叫onCount指向的函式,這樣相當於把事件通知出去。所以上面這個Demo之後,我們再可以對委託來實現事件進行擴充套件: 我們自定義一個雙擊火箭手按鈕[使用者控制元件]:
namespace 雙擊火箭手按鈕
{
//宣告一個委託
public delegate void DoubleClickDelegate();
public partial class DoubleClickButton : UserControl
{
public DoubleClickButton()
{
InitializeComponent();
}
private int i;
//把一個的委託物件定義為Public欄位
public DoubleClickDelegate OnDoubleClick;
private void button1_Click(object sender, EventArgs e)
{
i++;
if (i==2)
{
i = 0;
if (OnDoubleClick!=null)
{
OnDoubleClick();//呼叫事件的響應函式
}
}
}
}
}
然後我們在Form1中託入一個我們自定義的DoubleClickButton:
這兒我們可以仿照普通Button按鈕監聽Click事件: this.button1.Click += new System.EventHandler(this.button1_Click);來那樣做:對DoubleClickButton的
OnDoubleClick事件進行監聽:
private void Form1_Load(object sender, EventArgs e)
{
doubleClickButton1.OnDoubleClick = doFire;
doubleClickButton1.OnDoubleClick += doIce; }
void doFire()
{
MessageBox.Show("雙擊火槍手開火");
}
void doIce()
{
MessageBox.Show("雙擊火槍手下冰雨");
}
這樣一個簡單的使用者控制元件就完成了,雙擊兩下觸發了OnDoubleClick事件,並且去執行相關聯的響應函式(doFire,doIce)。接著我們在對這個程式進行修改,來干擾我們的雙擊火槍手按鈕:
我們在搗亂這個按鈕的中寫入:
private void button1_Click(object sender, EventArgs e)
{
doubleClickButton1.OnDoubleClick = null;
}
上面的操作意味著我們把委託變數指向了NULL,這就破壞了之前我們的監聽事件。再比如:在模擬執行這個按鈕中寫入:
private void button2_Click(object sender, EventArgs e)
{
doubleClickButton1.OnDoubleClick();
}
上面程式碼模擬執行了雙擊火槍後按鈕,本來需要雙擊兩下才能觸發事件,而這兒可以直接去執行事件的響應函式。
所以為了防止外界對我的事件的干擾,我們把
public OnCountDelegate onCount;//把委託物件宣告為一個欄位
改為:
//把委託申明為Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
private DoubleClickDelegate OnDoubleClick;
再對私有的委託用一個AddDoubleClick進行對外界的過濾,所以完整程式碼應該是這樣的:
//把委託申明為Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
private DoubleClickDelegate _OnDoubleClick;
public void AddDoubleClick(DoubleClickDelegate d)
{
_OnDoubleClick += d;
}
private void button1_Click(object sender, EventArgs e)
{
i++;
if (i==2)
{
i = 0;
if (_OnDoubleClick!=null)
{
_OnDoubleClick();//呼叫事件的響應函式
}
}
}
上面這樣處理之後,我們的控制元件就不會被外界干擾了。
但是這樣操作太複雜,所以微軟為我們提供了更為簡便的方式既 Event:
對上面的雙擊火槍手按鈕在做稍微修改:
namespace 三擊暴走按鈕
{
public delegate void RampageDelegate();
public partial class RampageThreeClickButton : UserControl
{
//定義一個私有的委託型別欄位
private RampageDelegate onRampage;
//類似於屬性那樣對委託進行了封裝
public event RampageDelegate OnRampage
{
add { onRampage += value; }
remove { onRampage -= value; }
}
private int count;
public RampageThreeClickButton()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
count++;
if (count==3)
{
if (onRampage!=null)
{
onRampage();
}
count = 0;
}
}
}
}
再將定義好的暴走控制元件拖到Form1中:
後臺程式碼是這樣的:
public Form1()
{
InitializeComponent();
}
private void rampageThreeClickButton1_Load(object sender, EventArgs e)
{
//對事件進行註冊,即監聽事件,只能用+=,不用用=,這樣防止了外界=NULL干擾
rampageThreeClickButton1.OnRampage += doFire;
//同理
rampageThreeClickButton1.OnRampage += new RampageDelegate(doThunder);
rampageThreeClickButton1.OnRampage += new RampageDelegate(doIce);
}
void doFire()
{
MessageBox.Show("烈焰之怒");
}
void doThunder()
{
MessageBox.Show("上古雷霆");
}
void doIce()
{
MessageBox.Show("極地冰雨");
}
現在還能干擾嗎?當然不行:
OK,到目前為止,大家對委託和事件肯定有一個深刻的認識:
我們對事件進行反編譯之後,相信一切疑問皆是浮雲:
我們舉一個標準的事件案例:
namespace DoubleKissinUButton
{
public delegate void KissinUDelegate();//委託別忘記
public partial class KissinUButton : UserControl
{
public event KissinUDelegate onKissinU;//用Event關鍵字來申明一個事件
public KissinUButton()
{
InitializeComponent();
}
private int i = 0;
private void button1_Click(object sender, EventArgs e)
{
i++;
if (i==2)
{
if (onKissinU!=null)
{
onKissinU();
}
}
}
}
}
介面:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
kissinUButton1.onKissinU += new KissinUDelegate(kissinUButton1_onKissinU);
}
void kissinUButton1_onKissinU()
{
MessageBox.Show("點我我就吻吻你");
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
通過反編譯看看:
點選進入看看:
委託和事件沒有可比性,因為委託是型別,事件是物件,上面說的是委託的物件(用委託方式實現的事件)和(標準的event方式實現)事件的區別。事件的內部是用委託實現的。 因為對於事件來講,外部只能“註冊自己+=、登出自己-=”,外界不可以登出其他的註冊者,外界不可以主動觸發事件,因此如果用Delegate就沒法進行上面的控制,因此誕生了事件這種語法。add、remove。 事件是用來閹割委託例項的。事件只能add、remove自己,不能賦值。事件只能+=、-=,不能=、不能外部觸發事件。