不像Windows API中使用C語言風(fēng)格的函數(shù)指針這種不安全的方式進行回調(diào)。.Net中此功能使用使用更為安全和面向?qū)ο蟮奈?delegate)來完成,。委托是一個類型安全的對象,它指向程序中另一個以后會被調(diào)用的方法(或多個方法),。 委托類型包含3個重要信息: 它所調(diào)用的方法的名稱 該方法的參數(shù)(可選) 該方法的返回值(可選)
當上述信息被提供后,,委托可以在運行時動態(tài)調(diào)用其指向的方法。很重要的一點:.Net中每個委托都被自動賦予同步或異步訪問方法的能力,。 定義委托 在C#中使用delegate關(guān)鍵字創(chuàng)建一個委托,。我們稱這種類為委托類。委托類的實例成為委托對象,。從概念上說,,委托對象是一種指向一個或多個方法(靜態(tài)或非靜態(tài))的引用。要求是此委托匹配它指向的方法的簽名,。 如下委托可以指向一任何傳入兩個整數(shù)返回一個整數(shù)的方法,。 | public delegate int BinaryOp(int x, int y); | 定義委托后,系統(tǒng)生成一個派生自MulticastDelegate類的密封類,。此類中有3個方法: Invoke()方法,,用來以同步方式調(diào)用委托維護的每個方法。(不能在C#中顯示調(diào)用此方法,,Invoke()在后臺被調(diào)用) BeginInvoke()與EndInvoke()方法在第二個線程上異步調(diào)用當前方法,。
開發(fā)人員創(chuàng)建第二個執(zhí)行線程的原因調(diào)用比較耗時的方法。(相當于委托順帶實現(xiàn)了一些System.Threading命名空間管理的線程問題) 這個委托的密封類大概如下: | sealed class BinaryOp : System.MulticastDelegate { public BinaryOp(object target, uint functionAddress); public int Invoke(int x, int y); public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state); public int EndInvoke(IAsyncResult result); } | 下面給一個簡單的委托示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // 這個委托指向任何一個傳入兩個整數(shù)并返回一個整數(shù)的方法 public delegate int BinaryOp(int x, int y); #region SimpleMath class public class SimpleMath { public int Add(int x, int y) { return x + y; } public int Subtract(int x, int y) { return x - y; } public static int SquareNumber(int a) { return a * a; } } #endregion class Program { static void Main(string[] args) { Console.WriteLine('***** Simple Delegate Example *****n'); // 創(chuàng)建一個指向SimpleMath.Add()方法的BinaryOp對象 SimpleMath m = new SimpleMath(); BinaryOp b = new BinaryOp(m.Add); // 使用委托調(diào)用調(diào)用Add()方法 // 此處也是Invoke()被調(diào)用的位置 Console.WriteLine('n10 + 10 is {0}', b(10, 10)); Console.ReadLine(); } } | 委托類型安全的體現(xiàn) 如果傳入一個與委托聲明不匹配的方法,,將在編譯時報錯,。如上例中如果傳入int SquareNumber(int),,將會導(dǎo)致一個編譯時錯誤。 獲取委托中調(diào)用函數(shù)列表的方法,,示例: 假設(shè)有名為delObj的委托對象,使用如下方式得到調(diào)用函數(shù)的信息 | Delegate d in delObj.GetInvocationList() Console.WriteLine('Method Name: {0}', d.Method); Console.WriteLine('Target Name: {0}', d.Target); | Method屬性表示調(diào)用的函數(shù)的簽名,,Target表示調(diào)用的函數(shù)所在的對象的類型名,,所以如果委托調(diào)用的是一個靜態(tài)方法則Target不會有任何顯示,只有當委托調(diào)用的是一個實例方法時,,Target屬性才有值,。 更完整的委托應(yīng)用(示例來自C#與.Net3.0高級程序設(shè)計),代碼: 汽車類: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | using System; using System.Collections.Generic; using System.Text; namespace CarDelegate { public class Car { // 定義委托類型 public delegate void AboutToBlow(string msg); public delegate void Exploded (string msg); // 定義各自委托類型的對象 private AboutToBlow almostDeadList; private Exploded explodedList; // 將成員添加到調(diào)用列表 public void OnAboutToBlow(AboutToBlow clientMethod) { almostDeadList += clientMethod; } public void OnExploded(Exploded clientMethod) { explodedList += clientMethod; } // 由調(diào)用列表移除方法 public void RemoveAboutToBlow(AboutToBlow clientMethod) { almostDeadList -= clientMethod; } public void RemoveExploded(Exploded clientMethod) { explodedList -= clientMethod; } // 內(nèi)部狀態(tài)成員 private int currSpeed; private int maxSpeed; private string petName; // 汽車壞了嗎,? bool carIsDead; public Car() { maxSpeed = 100; } public Car(string name, int max, int curr) { currSpeed = curr; maxSpeed = max; petName = name; } public void SpeedUp(int delta) { // 如果汽車壞了,,觸發(fā)Exploded事件 if (carIsDead) { if (explodedList != null) explodedList('Sorry, this car is dead'); } else { currSpeed += delta; // 幾乎要壞了? if (10 == maxSpeed - currSpeed && almostDeadList != null) { almostDeadList('Careful buddy! Gonna blow!'); } // 還好,! if (currSpeed >= maxSpeed) carIsDead = true; else Console.WriteLine('->CurrSpeed = {0}', currSpeed); } } } } | 主函數(shù): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | namespace CarDelegate { class Program { static void Main(string[] args) { // 制造一輛車 Car c1 = new Car('SlugBug', 100, 10); // 注冊事件處理函數(shù) Car.Exploded d = new Car.Exploded(CarExploded); c1.OnAboutToBlow(new Car.AboutToBlow(CarIsAlmostDoomed)); c1.OnAboutToBlow(new Car.AboutToBlow(CarAboutToBlow)); c1.OnExploded(d); // 加速 (這將觸發(fā)事件) Console.WriteLine('n***** 加速 *****'); for (int i = 0; i < 6; i++) c1.SpeedUp(20); // 由調(diào)用列表移除CarExploded方法 c1.RemoveExploded(d); Console.WriteLine('n***** 加速 *****'); for (int i = 0; i < 6; i++) c1.SpeedUp(20); Console.ReadLine(); } public static void CarAboutToBlow(string msg) { Console.WriteLine(msg); } public static void CarIsAlmostDoomed(string msg) { Console.WriteLine('Critical Message from Car: {0}', msg); } public static void CarExploded(string msg) { Console.WriteLine(msg); } } } | 對多路廣播的支持 .Net委托內(nèi)置多路,,即一個委托可以維護一個可調(diào)用方法的列表而不只是單獨一個方法,使用重載過的+=運算符可以向一個委托對象添加多個方法,。關(guān)于對對路廣播的支持可以參考上述示例,。 在多路廣播的支持中有一個需要注意的問題,一個委托調(diào)用的多個方法需要無參數(shù)且無返回值,,因為在調(diào)用委托時,,即使傳入了參數(shù)也不知道具體應(yīng)該傳給哪一個方法,即使這些方法有返回值也不知道該接受那個函數(shù)的返回值,。所以說直接不要調(diào)用有參數(shù)及返回值的方法,,這點與事件關(guān)聯(lián)多個事件處理方法時對處理方法簽名的要求相同(可以參見本系列介紹事件的文章)。 注意:我們可以用調(diào)用方法的語法”調(diào)用”委托對象,。這樣會調(diào)用委托對象所引用的方法,。(事件的觸發(fā)與委托的調(diào)用相同,本來事件就是一個委托類型的對象),。這些方法的調(diào)用是在調(diào)用委托的方法所在的線程中完成的,。這種調(diào)用稱同步調(diào)用。 C#2.0編譯器的委托類推測功能 C#編譯器引入了在創(chuàng)建委托變量時可以推測其類型的能力,。這樣就可以將一個方法賦給隱式創(chuàng)建的委托對象,。 示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class Program { delegate void Deleg1(); delegate string Deleg2( string s ); static void f1() { System.Console.WriteLine('f1() called.'); } static string f2(string s) { string _s=string.Format( 'f2() called with the param '{0}'.' , s ); System.Console.WriteLine( _s ); return _s; } public static void Main() { Deleg1 d1 = f1; // 代替 Deleg1 d1 = new Deleg1( f1 ); d1(); Deleg2 d2 = f2; // 代替 Deleg2 d2 = new Deleg2( f2 ); string s = d2('hello'); } } | 委托協(xié)變(covariance) 允許創(chuàng)建一個委托,其返回的對象的類型是繼承關(guān)系的,,示例代碼: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | // 簡單的集成關(guān)系的兩個類 class Car { public override string ToString() { return 'A stateless car'; } } class SportsCar : Car { public override string ToString() { return 'A stateless sports car'; } } class Program { // 定義一個返回Car或SportsCar的委托 public delegate Car ObtainVehicalDelegate(); // 委托指向目標 public static Car GetBasicCar() { return new Car(); } public static SportsCar GetSportsCar() { return new SportsCar(); } static void Main(string[] args) { ObtainVehicalDelegate targetA = new ObtainVehicalDelegate(GetBasicCar); Car c = targetA(); Console.WriteLine(c); // 協(xié)變允許指定這樣的目標方法 ObtainVehicalDelegate targetB = new ObtainVehicalDelegate(GetSportsCar); SportsCar sc = (SportsCar)targetB(); Console.WriteLine(sc); Console.ReadLine(); } } | 委托逆變,,其中參數(shù)具有集成關(guān)系,委托簽名的參數(shù)類型(派生類型)比方法具有的參數(shù)類型(基類型)更具體,。定義一個參數(shù)類型是派生類型的委托,,這個委托可以接收具有基類型參數(shù)的方法,因為派生類型隱式轉(zhuǎn)換成了基類型。注意此方法必須接收與委托簽名相同的參數(shù)類型(派生類型),,雖然方法的簽名中參數(shù)是基類型,。 接下來說一下委托在多線程程序中的應(yīng)用,主角有兩個:ThreadStart和ParameterizedThreadStart,,它們都定義與System.Threading命名空間下,。 使用這兩個委托,你可以以編程方式創(chuàng)建此線程來分擔一些任務(wù),,步驟如下: 創(chuàng)建一個方法作為新線程的入口點,。 創(chuàng)建一個ParameterizedThreadStart(或ThreadStart)委托,并把之前所定義的方法傳給委托的構(gòu)造函數(shù),。 創(chuàng)建一個Thread對象,,并把ParameterizedThreadStart或ThreadStart委托作為構(gòu)造函數(shù)的參數(shù)。 建立任意初始化線程的特性(名稱,、優(yōu)先級等),。 調(diào)用Thread.Start()方法。
完成上述步驟,,在第2步建立的委托所指向的方法將在線程中盡快開始執(zhí)行,。 ThreadStart委托指向一個沒有參數(shù)、無返回值的方法,,它在調(diào)用一個被設(shè)計用來僅僅在后臺運行,、而沒有更多的交互時非常有用。它的局限在于無法給這個函數(shù)出入?yún)?shù),,所以在.Net2.0中出現(xiàn)了ParameterizedThreadStart了方法,,它可以接受一個包含了任意個數(shù)的參數(shù)(傳給它要調(diào)用的方法的)的Object類型對象做參數(shù)(即允許用戶為新線程要執(zhí)行的方法傳入一個對象作為參數(shù))。但注意這兩種委托指向的函數(shù)的返回值都必須是void,。 看看示例代碼,,首先是ThreadStart委托的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class Printer { public void PrintNumbers() { //具體實現(xiàn)省略 } } class Program { static void Main(string[] args) { Printer p = new Printer(); Thread bgroundThread = new Thread(new ThreadStart(p.PrintNumbers)); // 控制此線程是否在后臺運行? bgroundThread.IsBackground = true; bgroundThread.Start(); } } | 接下來的代碼示例了ParameterizedThreadStart委托的使用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //包裝參數(shù)的類 class AddParams { public int a; public int b; public AddParams(int numb1, int numb2) { a = numb1; b = numb2; } } class Program { static void Main(string[] args) { Console.WriteLine('主線程ID:{0}', Thread.CurrentThread.GetHashCode()); //生成要傳入的參數(shù) AddParams ap = new AddParams(10, 10); Thread t = new Thread(new ParameterizedThreadStart(Add)); t.Start(ap); } //ParameterizedThreadStart委托調(diào)用的方法 //使用AddParams類對象做參數(shù) public static void Add(object data) { if (data is AddParams) { Console.WriteLine('后臺線程ID: {0}', Thread.CurrentThread.GetHashCode()); AddParams ap = (AddParams)data; Console.WriteLine('{0} + {1} is {2}', ap.a, ap.b, ap.a + ap.b); } } } | 詳細通過上面兩段簡單的代碼示例,,你已經(jīng)對ThreadStart和ParameterizedThreadStart的使用有了全面的了解,。 另外有一點需要說的,有些情況下可以省略這個委托對象的構(gòu)造,,即構(gòu)造Thread對象時,,直接向Thread的構(gòu)造函數(shù)傳入一個方法的名稱,而不用先構(gòu)造一個委托的對象,。傳入的方法既可以是靜態(tài)方法也可以是實例方法,。 另外委托在異步編程中的作用見異步編程的文章 參考資料: C#與.Net3.0高級程序設(shè)計 C#與.Net2.0實戰(zhàn) CLR via C# 第二版
|