這篇博文我不講委託和事件的概念,因爲大段的文字概念沒有任何意義。
具體想了解,委託和事件的概念可以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自己,不能賦值。事件只能+=、-=,不能=、不能外部觸發事件。