一、委托
委托的本質:
委托是一種特殊的數據類型,它表示某種特定類型的函數,並且可以表示多個函數,將這些函數串聯起來。使用委托就好像函數調用一樣。
委托實質上是一個類,編譯器會根據關鍵字delegate自動生成一個從System.Delegate類派生的類。所以,它具有可訪問性,public, private等,也包含幾個默認的成員函數和屬性。(這些可通過IL代碼看出編譯器為委托生成的具體的類名稱和代碼)
委托的作用:
委托時一種在C#中實現函數動態調用的方式,通過委托可以將一些相同類型的函數串聯起來依次執行。委托同時還是函數回調和事件機制的基礎。
在函數調用時,委托鏈上可以具有相同的函數,只要通過“+=”操作添加到委托鏈上即可。
委托的定義:
delegate return_type DelegateName(Type1 para1, Type2 para2, ... ,[TypeN paraN]);
如
delegate float DFloatFunc(int val1, float val2); // 定義一個委托類型
泛型委托
如果你知道泛型,那么就很容易理解泛型委托,說白了就是含有泛型參數的委托,例如:
public delegate T Calculator<T> (T arg);
我們可以把前面的例子改成泛型的例子,如下:
public delegate T Calculator<T>(T arg); class Program { static int Double(int x) { return x * 2; } static void Main(string[] args) { int[] values = { 1, 2, 3, 4 }; Utility.Calculate(values, Double); foreach (int i in values) Console.Write(i + " "); // 2 4 6 8 Console.ReadKey(); } } class Utility { public static void Calculate<T>(T[] values, Calculator<T> c) { for (int i = 0; i < values.Length; i++) values[i] = c(values[i]); } }
Func 和 Action 委托
有了泛型委托,就有了一能適用於任何返回類型和任意參數(類型和合理的個數)的通用委托,Func 和 Action。如下所示(下面的in表示參數,out表示返回結果):
delegate TResult Func <out TResult> (); delegate TResult Func <in T, out TResult> (T arg); delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2); ... 一直到 T16 delegate void Action (); delegate void Action <in T> (T arg); delegate void Action <in T1, in T2> (T1 arg1, T2 arg2); ... 一直到 T16
有了這樣的通用委托,我們上面的Calculator泛型委托就可以刪掉了,示例就可以更簡潔了:
public static void Calculate<T>(T[] values, Func<T,T> c) { for (int i = 0; i < values.Length; i++) values[i] = c(values[i]); }
Func 和 Action 委托,除了ref參數和out參數,基本上能適用於任何泛型委托的場景,非常好用。
參數類型兼容
在OOP中,任何使用父類的地方均可以用子類代替,這個OOP思想對委托的參數同樣有效。如:
delegate void StringAction(string s);
class Program {
static void Main() {
StringAction sa = new StringAction(ActOnObject);
sa("hello");
}
static void ActOnObject(object o) {
Console.WriteLine(o); // hello
}
}
二、事件理論基礎
當我們使用委托場景時,我們很希望有這樣兩個角色出現:廣播者和訂閱者。我們需要這兩個角色來實現訂閱和廣播這種很常見的場景。
廣播者這個角色應該有這樣的功能:包括一個委托字段,通過調用委托來發出廣播。而訂閱者應該有這樣的功能:可以通過調用 += 和 -= 來決定何時開始或停止訂閱。
事件就是描述這種場景模式的一個詞。事件是委托的一個子集,為了滿足“廣播/訂閱”模式的需求而生。
事件建立在委托機制之上,通過該機制,某個類在發生某些特定的事情之后,通知其它類或對象正在發生的事情。
事件(委托)
從本質上來說,事件其實就是委托,但是它通常是特定類型的的函數類型,具有以下特點:
事件發行者(類)確定何時引發事件,事件訂閱者確定如何響應該事件。
一個事件可以有多個訂閱者。一個訂閱者可以處理來自多個發行者的多個事件。
沒有訂閱者的事件,永遠不會被調用。
如果一個事件有多個訂閱戶,當引發該事件時,會同步調用多個事件處理程序。
在.NET類庫中,事件是基於EventHandle委托和EventArgs基類的。
事件響應函數委托
其通常沒有返回值,有sender 和 arg兩個參數。在定義一個事件之前,要先定義事件的參數類型,該類型包含了事件發起者需要提供給事件訂閱者的信息。
引發事件
實際上就是調用委托變量。但是在事件被定義之后,該變量默認為null, 直接引發會產生異常,所以調用之前要判斷事件是否為null(是否已經被訂閱)。通常通過定義OnXXX()的函數來引發XXX事件,在該函數中首先判斷事件是否被訂閱,如果被訂閱則引發該事件。
訂閱和處理事件
此方面的一個核心元素是事件響應函數。事件響應函數是符合呀哦訂閱的事件委托類型的函數,它通常根據事件的引發者和參數進行相應的處理。
由於事件的本質是委托,所以事件的訂閱實際上通過“+=”運算將當前類的事件響應函數添加到時間段額委托鏈中,在引發事件時就可以調用該處理函數。
委托的使用
例
聲明一個事件
聲明一個事件很簡單,只需在聲明一個委托對象時加上event關鍵字就行。如下:
public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice); public class IPhone6 { public event PriceChangedHandler PriceChanged; }
事件的使用和委托完全一樣,只是多了些約束。下面是一個簡單的事件使用例子:
public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice); public class IPhone6 { decimal price; public event PriceChangedHandler PriceChanged; public decimal Price { get { return price; } set { if (price == value) return; decimal oldPrice = price; price = value; // 如果調用列表不為空,則觸發。 if (PriceChanged != null) PriceChanged(oldPrice, price); } } } class Program { static void Main() { IPhone6 iphone6 = new IPhone6() { Price = 5288 }; // 訂閱事件 iphone6.PriceChanged += iphone6_PriceChanged; // 調整價格(事件發生) iphone6.Price = 3999; Console.ReadKey(); } static void iphone6_PriceChanged(decimal oldPrice, decimal price) { Console.WriteLine("年終大促銷,iPhone 6 只賣 " + price + " 元, 原價 " + oldPrice + " 元,快來搶!"); } }
有人可能會問,如果把上面的event關鍵字拿掉,結果不是一樣的嗎,到底有何不同?
沒錯可以用事件的地方就一定可以用委托代替。
但事件有一系列規則和約束用以保證程序的安全可控,事件只有 += 和 -= 操作,這樣訂閱者只能有訂閱或取消訂閱操作,沒有權限執行其它操作。如果是委托,那么訂閱者就可以使用 = 來對委托對象重新賦值(其它訂閱者全部被取消訂閱),甚至將其設置為null,甚至訂閱者還可以直接調用委托,這些都是很危險的操作,廣播者就失去了獨享控制權。
事件保證了程序的安全性和健壯性。
參考資料
[ASP.NET MVC 大牛之路]02 - C#高級知識點概要(1) - 委托和事件