1、什么是委托
當需要把方法傳遞給其他方法時,就需要使用委托。
我們習慣於把數據作為參數傳遞給方法,而有時某個方法執行的操作並不是針對數據進行的,而是要對另外一個方法進行調用。更麻煩的是,在編譯時我們是不知道第二個方法是什么的,這個信息只能在運行時得到,所以需要把第二個方法作為參數傳遞給第一個方法。例如:
1、啟動線程和任務——基類System.Threading.Thread的實例調用方法Start(),就可以啟動一個線程。如果告訴計算機啟動一個新實例,就必須說明要在哪里啟動該序列;必須為計算機提供開始啟動的方法和細節,即Thread類的構造函數必須帶有一個參數(該參數定義了線程調用的方法)。
2、事件——一般的思路是通知代碼發生了什么事件。GUI編程主要處理事件。引發事件時,運行庫需要知道應該執行哪個方法。這就需要把處理事件的方法作為參數傳遞給委托。
c++中,函數指針是一個指向內存地址的指針。但它不是類型安全的(無法判斷這個指針實際指向什么,更無從知曉參數和返回類型了),可以把任何函數傳遞給需要函數指針的方法。這不僅會導致一些類型安全問題,而且沒有意識到:在進行面向對象編程時,幾乎沒有方法時孤立存在的,而是在調用方法前通常需要和類的實例相關聯。所以.NET framework在語法上不允許使用這種直接方法。如果要傳遞方法,就必須把方法的細節封裝在一種新的對象類型中,即委托。委托只是一種特殊類型的對象——我們之前定義的所有對象都包含數據,而委托包含的只是一個或多個方法的地址。
委托就是尋址方法的.NET版本。
2、聲明委托
一般的,C#中使用一個類,分為兩個階段:1、定義一個類(即告訴編譯器這個類由什么字段和方法組成);2、實例化該類的一個對象(除非只使用靜態方法)。
使用委托時,也需要這兩個步驟:1、定義委托(即告訴編譯器這種委托表示哪種類型的方法);2、創建該委托的一個或多個實例(編譯器將在后台創建表示這個委托的一個類)
聲明委托的示例1:
delegate void DelegateNameInvoker(int x);
這個示例中,聲明了一個委托DelegateNameInvoker,它指定該委托的每個示例都可以包含一個方法的引用,該方法帶有一個int參數,並返回void。定義委托時,必須給出它所表示的方法的簽名和返回類型等全部細節(類型安全)。理解委托的一個好方式是把委托視為給方法的簽名和返回類型指定名稱。
示例2:
delegate double TwoLongsOption(long first , long second);
定義了一個委托TwoLongsOption,該委托表示的方法有兩個long參數,返回類型是double。
3、使用委托
定義好委托后,就可以創建它的一個實例,從而用該實例存儲特定方法的細節。
下面的代碼說明了如何調用委托:這是一個在int值上調用ToString()方法的一個相當冗長的方式。
class Program { private delegate string GetAString(); static void Main(string[] args) { int x = 40; GetAString getAString = new GetAString(x.ToString); Console.WriteLine(getAString()); } }
這段代碼,用new實例化了類型為GetAString的委托,並對它進行初始化(引用整形變量x的ToString()方法)。委托在語法上總是接受一個參數的構造函數,這個參數就是委托引用的方法,且這個方法必須匹配委托的簽名。
實際上,給委托實例提供圓括號與調用委托類的Invoke()方法完全相同。
委托推斷:為了減少輸入量,在需要委托實例(即new的時候)的每個位置可以只傳送地址的名稱。,下面的例子就是一個例子:
1 GetAString getAString1 = x.ToString;
getAString1和getAString表示相同的含義。
注意:輸入形式不能為x.ToString()。x.ToString()會返回一個字符串對象。只能把方法的地址賦予給委托。
委托是類型安全的,但有趣的是,它不關心在什么類型的對象上調用該方法,甚至不考慮方法時靜態方法還是實例方法(只要方法的簽名匹配委托的簽名即可)。
下面的代碼,擴展了上面的例子,委托getAString在另外一個對象上調用其他2個方法,一個是實例方法,一個靜態方法:
1 struct Currency 2 { 3 public uint Dollars; 4 public ushort Cents; 5 public Currency(uint dollars,ushort cents) 6 { 7 Dollars = dollars; 8 Cents = cents; 9 } 10 //實例方法 11 public override string ToString() 12 { 13 return $"{Dollars}.{Cents}"; 14 } 15 //GetCurrencyUint是一個靜態方法 16 public static string GetCurrencyUint() => "Dollar"; 17 } 18 private delegate string GetAString(); 19 static void Main(string[] args) 20 { 21 int x = 40; 22 GetAString getAString = new GetAString(x.ToString); 23 Console.WriteLine(getAString()); 24 25 var currency = new Currency(22, 11); 26 //調用實例方法 27 getAString = currency.ToString; 28 Console.WriteLine(getAString()); 29 //調用類的靜態方法 30 getAString = Currency.GetCurrencyUint; 31 Console.WriteLine(getAString()); 32 33 }
4、簡單的委托示例
下面的代碼描述了委托的其中一個小用途——把方法組合到一個數組中,這樣就可以在循環中調用不同的方法了:
1 class MathOperations 2 { 3 public static double MultiplyBy2(double value) => value * 2; 4 public static double Square(double value) => value * value; 5 } 6 delegate double DoubleOp(double x); 7 static void Main(string[] args) 8 { 9 DoubleOp[] doubleOps = 10 { 11 MathOperations.MultiplyBy2, 12 MathOperations.Square 13 }; 14 15 for(int i = 0; i < doubleOps.Length; i++) 16 { 17 Console.WriteLine($"調用方法{i}:"); 18 ProcessDisplay(doubleOps[i], 2.0); 19 ProcessDisplay(doubleOps[i], 2.2); 20 } 21 22 } 23 24 private static void ProcessDisplay(DoubleOp doubleOp, double v) 25 { 26 Console.WriteLine($"變量是:{v},結果是:{doubleOp(v)}"); 27 }
運行結果: