委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞,這種將方法動態地賦給參數的做法,可以避免在程序中大量使用If-Else(Switch)語句,同時使得程序具有更好的擴展性。
與使用一個類相似,在使用委托時,也需要經過兩個步驟:
- 定義要使用的委托。對於委托,定義它就是告訴編譯器這種類型的委托代表了哪種類型的方法(在定義委托是,必須給出它所代表的方法簽名和返回類型等全部細節。理解委托的一種好方式是把委托當作給方法簽名和返回類型指定名稱。)
delegate void IntMethodInoker(int x);
- 創建委托的一個或多個實例
1 using System; 2 3 namespace ConsoleApplication25 4 { 5 class Program 6 { 7 private delegate string GetAString(); 8 static void Main(string[] args) 9 { 10 int x = 40; 11 GetAString firstStringMethod = new GetAString(x.ToString); 12 Console.WriteLine("String is {0}",firstStringMethod()); 13 Console.ReadLine(); 14 } 15 } 16 17 18 }
提示:給定委托的實例可以表示任何類型的任何對象上的實例方法或靜態方法--只要方法的簽名匹配於委托的簽名即可。
下面的示例,讓firstStringMethod委托在另一個對象上調用其他兩個方法,其中一個是實例方法,另一個是靜態方法。
1 using System; 2 3 namespace ConsoleApplication26 4 { 5 class Program 6 { 7 private delegate string GetAString(); 8 static void Main(string[] args) 9 { 10 Currency balance = new Currency(34, 50); 11 12 // firstStringMethod references an instance method 13 GetAString firstStringMethod = new GetAString(balance.ToString); 14 Console.WriteLine("String is {0}", firstStringMethod()); 15 16 // firstStringMethod references a static method 17 firstStringMethod = new GetAString(Currency.GetCurrencyUnit); 18 Console.WriteLine("String is {0}", firstStringMethod()); 19 20 Console.ReadLine(); 21 } 22 } 23 24 struct Currency 25 { 26 public uint Dollars; 27 public ushort Cents; 28 public Currency(uint dollars, ushort cents) 29 { 30 this.Dollars = dollars; 31 this.Cents = cents; 32 } 33 34 public override string ToString() 35 { 36 return string.Format("${0}.{1,-2:00}", Dollars, Cents); 37 } 38 39 public static string GetCurrencyUnit() 40 { 41 return "Dollar"; 42 } 43 44 public static explicit operator Currency(float value) 45 { 46 checked 47 { 48 uint dollars = (uint)value; 49 ushort cents = Convert.ToUInt16((value - dollars) * 100); 50 return new Currency(dollars, cents); 51 } 52 } 53 54 public static implicit operator float(Currency value) 55 { 56 return value.Dollars + (value.Cents / 100.0f); 57 } 58 59 public static implicit operator Currency(uint value) 60 { 61 return new Currency(value, 0); 62 } 63 64 public static implicit operator uint(Currency value) 65 { 66 return value.Dollars; 67 } 68 } 69 }
示例2:
1 using System; 2 3 namespace ConsoleApplication28 4 { 5 class Program 6 { 7 delegate double DoubleOp(double x); 8 static void Main(string[] args) 9 { 10 DoubleOp[] operations = { MathsOperation.MultiplyByTwo, MathsOperation.Square }; 11 for (int i = 0; i < operations.Length; i++) 12 { 13 Console.WriteLine("Using operations[{0}]:", i); 14 ProcessAndDisplayNumber(operations[i], 3.0); 15 } 16 Console.ReadLine(); 17 } 18 19 static void ProcessAndDisplayNumber(DoubleOp action, double value) 20 { 21 double result = action(value); 22 Console.WriteLine("Value is {0}, result of the operation is {1}", value, result); 23 } 24 } 25 26 class MathsOperation 27 { 28 public static double MultiplyByTwo(double value) 29 { 30 return value * 2; 31 } 32 33 public static double Square(double value) 34 { 35 return value * value; 36 } 37 } 38 }
示例2的關鍵一行是把委托傳遞給ProcessAndDisplayNumber()方法
ProcessAndDisplayNumber(operations[i], 3.0);
其中傳遞了委托名,但不帶任何參數,假定operations[i]是一個委托,其語法是:
- operations[i]表示“這個委托”。換言之,就是委托代表的方法
- operations[i](2.0)表示“調用這個方法,參數放在括號中”。
示例3:
1 using System; 2 3 namespace ConsoleApplication29 4 { 5 delegate bool Comparison(object x, object y); 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Employee[] employees = { new Employee("a", 20000), new Employee("b", 10000), new Employee("c", 15000) }; 11 BubbleSorter.Sort(employees, new Comparison(Employee.CompareSalary)); 12 foreach (var employee in employees) 13 { 14 Console.WriteLine(employee); 15 } 16 Console.ReadLine(); 17 } 18 } 19 20 class BubbleSorter 21 { 22 static public void Sort(object[] sortArray, Comparison comparison) 23 { 24 for (int i = 0; i < sortArray.Length; i++) 25 { 26 for (int j = i; j < sortArray.Length; j++) 27 { 28 if (comparison(sortArray[j], sortArray[i])) 29 { 30 object temp=sortArray[i]; 31 sortArray[i]=sortArray[j]; 32 sortArray[j]=temp; 33 } 34 } 35 } 36 } 37 } 38 39 class Employee 40 { 41 private string name; 42 private decimal salary; 43 public Employee(string name, decimal salary) 44 { 45 this.name = name; 46 this.salary = salary; 47 } 48 49 public override string ToString() 50 { 51 return string.Format("{0},{1:C}", name, salary); 52 } 53 54 public static bool CompareSalary(object x, object y) 55 { 56 Employee e1 = (Employee)x; 57 Employee e2 = (Employee)y; 58 return (e1.salary < e2.salary); 59 } 60 } 61 }
輸出:
多播委托:當一個委托只包含一個方法調用,調用委托的次數與調用方法的次數相同,如果調用多個方法,就需要多次顯示調用這個委托。委托也可以包含多個方法,這種委托成為多播委托。
如果調用多播委托,就可以按順序連續調用多個方法。為此,委托的簽名就必須返回void,否則就只能得到委托調用的最后一個方法的結果。
1 using System; 2 3 namespace ConsoleApplication30 4 { 5 delegate void DoubleOp(double value); 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 DoubleOp operations = new DoubleOp(MathOperations.MultiplyByTwo); 11 operations += MathOperations.Square; 12 ProcessAndDisplayNumber(operations, 3.0); 13 Console.ReadLine(); 14 } 15 16 static void ProcessAndDisplayNumber(DoubleOp action, double valueToProcess) 17 { 18 Console.WriteLine(); 19 Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", valueToProcess); 20 action(valueToProcess); 21 } 22 } 23 24 class MathOperations 25 { 26 public static void MultiplyByTwo(double value) 27 { 28 double result = value * 2; 29 Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result); 30 } 31 32 public static void Square(double value) 33 { 34 double result = value * value; 35 Console.WriteLine("Squaring: {0} gives {1}", value, result); 36 } 37 } 38 }
注意:如果使用多播委托,就應注意對同一個委托調用方法鏈的順序並未正式定義,一次應避免編寫依賴於以特定順序調用方法的代碼。
通過一個委托調用多個方法還有一個問題,多播委托包含一個諸葛調用的委托集合,如果通過委托調用的一個方法拋出了異常,整個迭代就會停止。例如:
1 using System; 2 3 namespace ConsoleApplication31 4 { 5 public delegate void DemoDelegate(); 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 DemoDelegate dl = new DemoDelegate(One); 11 dl += Two; 12 try 13 { 14 dl(); 15 } 16 catch(Exception) 17 { 18 Console.WriteLine("Exception caught"); 19 } 20 Console.ReadLine(); 21 } 22 23 static void One() 24 { 25 Console.WriteLine("One"); 26 throw new Exception("Error in one"); 27 } 28 29 static void Two() 30 { 31 Console.WriteLine("Two"); 32 } 33 } 34 }
輸出:
委托只調用了第一個方法,第一個方法拋出了異常,所以委托的迭代會停止,不再調用Two()方法。
為了避免這個問題,應手動迭代方法列表。Delegate類定義了方法GetInvocationList(),它返回一個Delegate對象數組。
1 using System; 2 3 namespace ConsoleApplication31 4 { 5 public delegate void DemoDelegate(); 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 DemoDelegate dl = new DemoDelegate(One); 11 dl += new DemoDelegate(Two); 12 Delegate[] delegates = dl.GetInvocationList(); 13 for (int i = 0; i < delegates.Length; i++) 14 { 15 DemoDelegate d = (DemoDelegate)delegates[i]; 16 try 17 { 18 d(); 19 } 20 catch (Exception) 21 { 22 Console.WriteLine("Exception caught"); 23 } 24 } 25 Console.ReadLine(); 26 } 27 28 static void One() 29 { 30 Console.WriteLine("One"); 31 throw new Exception("Error in one"); 32 } 33 34 static void Two() 35 { 36 Console.WriteLine("Two"); 37 } 38 } 39 }
輸出:
匿名方法:到目前為止,要想使委托工作,方法必須已經存在。但是使用委托還有另外一種方式:即通過匿名方法。匿名方法是用做委托參數的一個代碼塊。
用匿名方法定義委托的語法和前面的定義並沒有區別,但是實例化委托時就有區別了。
1 using System; 2 3 namespace ConsoleApplication32 4 { 5 class Program 6 { 7 delegate string DelegateTest(string val); 8 static void Main(string[] args) 9 { 10 string mid = ", middle part,"; 11 DelegateTest anonDel = delegate(string param) 12 { 13 param += mid; 14 param += " and this was added to the string."; 15 return param; 16 }; 17 18 Console.WriteLine(anonDel("Start of string")); 19 Console.ReadLine(); 20 } 21 } 22 }
在Main方法中,定義anonDel時,不是傳送已知的方法名,而是使用一個簡單的代碼塊。
在使用匿名方法是,必須遵循兩個規則:在匿名方法中不能使用跳轉語句調到該匿名方法的外部;反之亦然:匿名方法外部的跳轉語句不能調到該匿名方法的內部。
- 在匿名方法內部不能訪問不完全的代碼
- 不能訪問在匿名方法外部使用的ref和out參數,但可以使用在匿名方法外部定義的其他變量
- 如果需要用匿名方法多次編寫同一個功能,就不要使用匿名方法,而編寫一個指定的方法比較好,因為該方法只能編寫一次,以后可通過名稱引用它
l表達式 : 為匿名方法提供了的一個新語法。
1 using System; 2 3 namespace ConsoleApplication33 4 { 5 class Program 6 { 7 delegate string DelegateTest(string val); 8 static void Main(string[] args) 9 { 10 string mid = ", middle part,"; 11 DelegateTest anonDel = param => 12 { 13 param += mid; 14 param += " and this was added to the string."; 15 return param; 16 }; 17 Console.WriteLine(anonDel("Start of the string")); 18 Console.ReadLine(); 19 } 20 } 21 }
運算符=>的左邊列出了匿名方法需要的參數。有幾種編寫方式:
1. 在括號中定義類型和變量名:
(string param) =>
2. 省去變量類型:
(param) =>
3. 如果只有一個參數,就可以刪除括號:
DelegateTest anonDel = param =>
表達式的右邊列出了實現代碼。如果實現代碼只有一行,也可以刪除花括號和return語句。
利用l表達式 來寫前面的多播委托示例,優點是它刪除了類:
1 using System; 2 3 namespace ConsoleApplication34 4 { 5 class Program 6 { 7 delegate double DoubleOp(double value); 8 static void Main(string[] args) 9 { 10 DoubleOp multByTwo = val => val * 2; 11 DoubleOp square = val => val * val; 12 13 DoubleOp[] operations = { multByTwo, square }; 14 15 for (int i = 0; i < operations.Length; i++) 16 { 17 Console.WriteLine("Using operations[{0}]", i); 18 ProcessAndDiaplayNumber(operations[i], 3.0); 19 } 20 21 Console.ReadLine(); 22 } 23 24 static void ProcessAndDiaplayNumber(DoubleOp action, double value) 25 { 26 double result= action(value); 27 Console.WriteLine("The result of the value: {0} is {1}", value, result); 28 } 29 } 30 }
返回類型協變:方法的返回類型可以派生於委托定義的類型。在下面的示例中,委托MyDelegate1定義為返回DelegateReturn類型。賦予委托實例d1的方法Method1返回DelegateReturn2類型,DelegateReturn2派生於DelegateReturn,因此滿足了委托的需求,這成為返回類型協變。
1 namespace ConsoleApplication35 2 { 3 public class DelegateReturn 4 { 5 } 6 7 public class DelegateReturn2 : DelegateReturn 8 { 9 } 10 11 public delegate DelegateReturn MyDelegate1(); 12 13 class Program 14 { 15 static void Main() 16 { 17 MyDelegate1 d1 = Method1; 18 d1(); 19 } 20 21 static DelegateReturn2 Method1() 22 { 23 DelegateReturn2 d2 = new DelegateReturn2(); 24 return d2; 25 } 26 } 27 }
參數類型協變:表示委托定義的參數可能不同於委托調用的方法。這里是返回類型不同,因為方法使用的參數類型可能派生自委托定義的類型。
1 namespace ConsoleApplication36 2 { 3 public class DelegateParam 4 { } 5 6 public class DelegateParam2 : DelegateParam 7 { } 8 9 public delegate void MyDelegate2(DelegateParam2 p); 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 MyDelegate2 d2 = Method2; 15 DelegateParam2 p = new DelegateParam2(); 16 d2(p); 17 } 18 19 static void Method2(DelegateParam p) 20 { } 21 } 22 }