一、什么是委托
簡單說它就是一個能把方法當參數傳遞的對象,而且還知道怎么調用這個方法,同時也是粒度更小的“接口”(約束了指向方法的簽名)。
委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞,是種將方法動態地賦給參數的做法。
用過C/C++的,對委托不會陌生,委托可以看成函數指針的升級版本!
函數指針簡介:
下面是一段C程序,Calc就是定義的函數指針。
typedef int (* Calc)(int a, int b); int Add(int a, int b) { int result = a + b; return result; } main() { int x = 100; int y = 200; int z = 0; Calc funcPoint1 = &Add; z = funcPoint1(x, y); printf("%d \n", z); }
這段程序很好的體現了一切皆地址的思想,變量和函數都是地址。
直接調用和間接調用的效果是一致的,都是訪問那個內存地址,委托相當於函數指針的升級版。
委托的簡單案例
一個委托類型定義了該類型的實例能調用的一類方法,這些方法含有同樣的返回類型和同樣參數(類型和個數相同)。
委托是一個類,所以要在類聲明的位置進行聲明,而不是寫在類里面,那樣就寫成嵌套類了。如下定義了一個委托類型 - Calculator:
delegate int Calculator (int x);
此委托適用於任何有着int返回類型和一個int類型參數的方法,如:
static int Double (int x) { return x * 2; }
創建一個委托實例,將該此方法賦值給該委托實例:
Calculator c = new Calculator(Double);
也可以簡寫成:
Calculator c = Double;
這個方法可以通過委托調用:
int result = c(2);
下面是完整代碼:
delegate int Calculator(int x); class Program { static int Double(int x) { return x * 2; } static void Main(string[] args) { Calculator c = Double; //c 就是委托實例, int result = c(2); Console.Write(result); Console.ReadKey(); } }
二、委托的一般使用
2.1用委托實現插件式編程
我們可以利用“委托是一個能把方法作為參數傳遞的對象”這一特點,來實現一種插件式編程。
例如,我們有一個Utility類,這個類實現一個通用方法(Calculate),用來執行任何有一個整型參數和整型返回值的方法。這樣說有點抽象,下面來看一個例子:
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) { // Calculator c 是簡單委托的變種寫法,就是把實例化放在了形參定義的語句里 //但是這個實例化具體對應的是什么方法,只有真的傳入參數的時候才知道! for (int i = 0; i < values.Length; i++) values[i] = c(values[i]); } }
這個例子中的Utility是固定不變的,程序實現了整數的Double功能。我們可以把這個Double方法看作是一個插件,如果將來還要實現諸如求平方、求立方的計算,我們只需向程序中不斷添加插件就可以了。
如果Double方法是臨時的,只調用一次,若在整個程序中不會有第二次調用,那么我們可以在Main方法中更簡潔更靈活的使用這種插件式編程,無需先定義方法,使用λ表達式即可,如:
...
Utility.Calculate(values, x => x * 2);
...
2.2多播委托
一個委托實例不僅可以指向一個方法,還可以指向多個方法。例如:
MyDelegate d = MyMethod1; // “+=” 用來添加,同理“-=”用來移除。 d += MyMethod2; // d -= MyMethod2
調用時,按照方法被添加的順序依次執行。注意,對於委托,+= 和 -= 對null是不會報錯的,如:
MyDelegate d; d += MyMethod1;// 相當於MyDelegate d = MyMethod1;
為了更好的理解多播在實際開發中的應用,我用模擬瞬聘網的職位匹配小工具來做示例。在職位匹配過程中會有一段處理時間,所以在執行匹配的時候要能看到執行的進度,而且還要把執行的進度和執行情況寫到日志文件中。在處理完一個步驟時,將分別執行兩個方法來顯示和記錄執行進度。
我們先定義一個委托(ProgressReporter),然后定義一個匹配方法(Match)來執行該委托中的所有方法。如下:
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); //線程暫停0.1s之后再繼續運行程序! } } } }
然后我們需要兩個監視進度的方法,一個把進度寫到Console,另一個把進度寫到文件。如下:
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 + "%"); } }
運行結果:
看到這里,是不是發現你已然更加愛上C#了。
2.3靜態方法和實例方法對於委托的區別
當一個類的實例的方法被賦給一個委托對象時,在上下文中不僅要維護這個方法,還要維護這個方法所在的實例。System.Delegate 類的Target屬性指向的就是這個實例。(也就是要在內存中維護這個實例,也就是可能的內存泄漏)
但對於靜態方法,System.Delegate 類的Target屬性是Null,所以將靜態方法賦值給委托時性能更優。
2.4泛型委托
如果你知道泛型,那么就很容易理解泛型委托,說白了就是含有泛型參數的委托,例如:
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]); } }
2.5Func 和 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 是對delegate的一種簡寫,更簡潔
Func 和 Action 委托,除了ref參數和out參數,基本上能適用於任何泛型委托的場景,非常好用。ACTION 和FUNC 最常用的兩種委托,類庫為我們准備好的!
action就是一種委托的簡便寫法,默認的是無返回值類型的方法,注意不要加括號,只是綁定地址,而不是執行!Func這種用來調用有返回值的委托!
直接調用方法,使用calculator.report();
間接調用,使用action.Invoke();
action();這種寫法是為了模仿函數指針的寫法。實際上還是默認調用invoke()
上面的案例寫的有些亂,而且是一種把func寫入函數參數的類型,不容易理解,下面再用另一個案例
static void Main(string[] args) { Func<string> RetBook = new Func<string>(FuncBook); Console.WriteLine(RetBook()); } public static string FuncBook() { return "送書來了"; }
無返回值
static void Main(string[] args) { Action<string> BookAction = new Action<string>(Book); BookAction("百年孤獨"); } public static void Book(string BookName) { Console.WriteLine("我是買書的是:{0}",BookName); }
2.6委托的異步調用
1、顯式異步調用
顯式異步調用 thread 或者 task
2、隱式異步調用
使用委托進行隱式異步調用,begininvoke就是隱式異步調用,它會開發分支線程,他有兩個參數。
aciont1.BeginInvoke(null, null);
EndInvoke
隱式調用也有兩種,一種是不使用回調函數的,另一種是使用的。
不使用回調函數

namespace delegate3 { class Program { //public delegate int AddHandler(int a, int b); public class Cal { public static int Add(int a, int b) { Console.WriteLine("開始計算:" + a + "+" + b); Thread.Sleep(3000); //模擬該方法運行三秒 Console.WriteLine("計算完成!"); return a + b; } } static void Main(string[] args) { Console.WriteLine("===== 異步調用 AsyncInvokeTest ====="); //AddHandler handler = new AddHandler(Cal.Add); //IAsyncResult: 異步操作接口(interface) //BeginInvoke: 委托(delegate)的一個異步方法的開始 //IAsyncResult result = handler.BeginInvoke(1, 2, null, null); Console.WriteLine("繼續做別的事情。。。"); Func<int,int,int> RetBook = new Func<int,int,int>(Cal.Add); //RetBook.BeginInvoke(1, 2); IAsyncResult result = RetBook.BeginInvoke(1, 2, null, null); //異步操作返回 Console.WriteLine(RetBook.EndInvoke(result)); Console.ReadKey(); } } }
使用回調函數

namespace CallBack { public delegate int AddHandler(int a, int b); public class Cal { public static int Add(int a, int b) { Console.WriteLine("開始計算:" + a + "+" + b); Thread.Sleep(3000); //模擬該方法運行三秒 Console.WriteLine("計算完成!"); return a + b; } } class Program { static void Main() { Console.WriteLine("===== 異步回調 AsyncInvokeTest ====="); AddHandler handler = new AddHandler(Cal.Add); //異步操作接口(注意BeginInvoke方法的不同!) IAsyncResult result = handler.BeginInvoke(1, 2, new AsyncCallback(回調函數), "AsycState:OK"); Console.WriteLine("繼續做別的事情。。。"); Console.ReadKey(); } static void 回調函數(IAsyncResult result) { //result 是“加法類.Add()方法”的返回值 //AsyncResult 是IAsyncResult接口的一個實現類,空間:System.Runtime.Remoting.Messaging //AsyncDelegate 屬性可以強制轉換為用戶定義的委托的實際類。 AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate; Console.WriteLine(handler.EndInvoke(result)); Console.WriteLine(result.AsyncState); } } }
三、委托的缺點
引用了某個方法,那么這個方法在內存中就不能釋放了,一旦釋放,委托就不能調用這個方法,所以委托有可能造成內存泄漏。(靜態方法不存在這個問題)