簡單說它就是一個能把方法當參數傳遞的對象,而且還知道怎么調用這個方法,同時也是粒度更小的“接口”(約束了指向方法的簽名)。
定義一個委托類型 Calculator:
delegate int Calculator(int x);//定義一個委托(此委托適用于任何有著int返回類型和一個int類型參數的方法) class Program { static int Double(int x) { return x * 2; }//定義一個方法 static void Main(string[] args) { //Calculator c = new Calculator(Double);//創建一個委托實例,將該此方法賦值給該委托實例 Calculator c = Double;//簡寫 int result = c(2);//通過委托調用方法 Console.Write(result);//打印 Console.ReadKey(); } }
我們可以利用“委托是一個能把方法作為參數傳遞的對象”這一特點,來實現一種插件式編程。
delegate int Calculator(int x); 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(int[] values, Calculator c) { for (int i = 0; i < values.Length; i++) values[i] = c(values[i]); } }
這個例子中的Utility是固定不變的,程序實現了整數的Double功能。
我們可以把這個Double方法看作是一個插件,如果將來還要實現諸如求平方、求立方的計算,我們只需向程序中不斷添加插件就可以了
如果Double方法是臨時的,只調用一次,使用lambda表達式即可:
...Utility.Calculate(values, x => x * 2);...
多播委托
一個委托實例不僅可以指向一個方法,還可以指向多個方法。
“+=” 用來添加,“-=”用來移除。
調用時,按照方法被添加的順序依次執行。
對于委托,+= 和 -= 對null是不會報錯的
MyDelegate d; d += MyMethod1; // 相當于MyDelegate d = MyMethod1;
示例:
public delegate void ProgressReporter(int percentComplete);//定義一個委托 public class Utility { //定義一個與之匹配的方法(用來執行該委托中的所有方法) public static void Match(ProgressReporter p) { if (p != null) { for (int i = 0; i <= 10; i++) { p(i * 10); System.Threading.Thread.Sleep(100); } } } } class Program { static void Main(string[] args) { ProgressReporter p = WriteProgressToConsole; p += WriteProgressToFile; Utility.Match(p); Console.WriteLine("Done."); Console.ReadKey(); } //把進度打印 static void WriteProgressToConsole(int percentComplete) { Console.WriteLine(percentComplete+"%"); } //把進度寫到文件 static void WriteProgressToFile(int percentComplete) { System.IO.File.AppendAllText("progress.txt", percentComplete + "%"); } }
靜態方法和實例方法對于委托的區別
當一個類的實例方法被賦給一個委托對象時,在上下文中不僅要維護這個方法,還要維護這個方法所在的實例。
但對于靜態方法,System.Delegate 類的Target屬性是Null,所以將靜態方法賦值給委托時性能更優。
//System.Delegate 類的Target屬性指向的就是這個實例 class Program { static void Main(string[] args) { X x = new X(); ProgressReporter p = x.InstanceProgress; p(1); Console.WriteLine(p.Target == x); // True Console.WriteLine(p.Method); // Void InstanceProgress(Int32)
} } class X { public void InstanceProgress(int percentComplete) { // do something
} }
泛型委托
含有泛型參數的委托
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 委托
能適用于任何返回類型和任意參數(類型和合理的個數)的通用委托(in表示參數,out表示返回結果)
除了ref參數和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
應用
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, Func<T,T> c) { for (int i = 0; i < values.Length; i++) values[i] = c(values[i]); } }
委托的兼容
類型兼容
delegate void D1(); delegate void D2(); D1 d1 = Method1; D2 d2 = d1; D2 d2 = new D2(d1);
對于具體相同的目標方法的委托是被視為相等的
同理,對于多播委托,如果含有相同的方法和相同的順序,也被視為相等
delegate void D(); D d1 = Method1; D d2 = Method1; Console.WriteLine (d1 == d2); // True
參數類型兼容
在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 } }
返回值類型兼容
delegate object ObjectRetriever(); class Program { static void Main() { ObjectRetriever o = new ObjectRetriever(RetriveString); object result = o(); Console.WriteLine(result); // hello
} static string RetriveString() {
return "hello";
} }
事件
當我們使用委托場景時,我們很希望有這樣兩個角色出現:廣播者和訂閱者。我們需要這兩個角色來實現訂閱和廣播這種很常見的場景。
廣播者這個角色應該有這樣的功能:包括一個委托字段,通過調用委托來發出廣播。而訂閱者應該有這樣的功能:可以通過調用 += 和 -= 來決定何時開始或停止訂閱。
事件就是描述這種場景模式的一個詞。事件是委托的一個子集,為了滿足“廣播/訂閱”模式的需求而生。
聲明一個事件,只需在聲明一個委托對象時,加上event關鍵字;
事件的使用和委托完全一樣,只是多了些約束;
可以用事件的地方就一定可以用委托代替;
事件保證了程序的安全性和健壯性。
事件有一系列規則和約束用以保證程序的安全可控,事件只有 += 和 -= 操作,這樣訂閱者只能有訂閱或取消訂閱操作,沒有權限執行其它操作。
如果是委托,那么訂閱者就可以使用 = 來對委托對象重新賦值(其它訂閱者全部被取消訂閱),甚至將其設置為null,甚至訂閱者還可以直接調用委托,這些都是很危險的操作,廣播者就失去了獨享控制權。
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 + " 元,快來搶!"); } }
事件的標準模式
.NET 框架為事件編程定義了一個標準模式。
設定這個標準是為了讓.NET框架和用戶代碼保持一致。
System.EventArgs是標準模式的核心,它是一個沒有任何成員,用于傳遞事件參數的基類。
//定義EventArgs public class PriceChangedEventArgs : System.EventArgs { public readonly decimal OldPrice; public readonly decimal NewPrice; public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) { OldPrice = oldPrice; NewPrice = newPrice; } } public class IPhone6 { decimal price; //為事件定義委托 //必須是 void 返回類型 //必須有兩個參數,且第一個是object類型,第二個是EventArgs類型(的子類) //名稱必須以EventHandler結尾 //由于考慮到每個事件都要定義自己的委托很麻煩,.NET 框架為我們預定義好一個通用委托System.EventHandler //public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs; //如果不使用框架的EventHandler,我們需要自己定義一個 //public delegate void PriceChangedEventHandler (object sender, PriceChangedEventArgs e); //如果不需要參數,可以直接使用EventHandler public event EventHandler<PriceChangedEventArgs> PriceChanged; //事件標準模式還需要寫一個受保護的虛方法來觸發事件,這個方法必須以On為前綴,加上事件名(PriceChanged),還要接受一個EventArgs參數 protected virtual void OnPriceChanged(PriceChangedEventArgs e) { if (PriceChanged != null) PriceChanged(this, e); } public decimal Price { get { return price; } set { if (price == value) return; decimal oldPrice = price; price = value; // 如果調用列表不為空,則觸發。 if (PriceChanged != null) OnPriceChanged(new PriceChangedEventArgs(oldPrice, price)); } } } class Program { static void Main() { IPhone6 iphone6 = new IPhone6() { Price = 5288M }; // 訂閱事件 iphone6.PriceChanged +=iphone6_PriceChanged; // 調整價格(事件發生) iphone6.Price = 3999; Console.ReadKey(); } static void iphone6_PriceChanged(object sender, PriceChangedEventArgs e) { Console.WriteLine("年終大促銷,iPhone 6 只賣 " + e.NewPrice + " 元, 原價 " + e.OldPrice + " 元,快來搶!"); } }
浙公網安備 33010602011771號