C#进阶之路(一):委托
一、什么是委托
簡單說它就是一個能把方法當參數傳遞的對象,而且還知道怎么調用這個方法,同時也是粒度更小的“接口”(約束了指向方法的簽名)。
委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞,是種將方法動態地賦給參數的做法。
用過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();}} } View Code使用回調函數
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);}} } View Code三、委托的缺點
?
引用了某個方法,那么這個方法在內存中就不能釋放了,一旦釋放,委托就不能調用這個方法,所以委托有可能造成內存泄漏。(靜態方法不存在這個問題)
?
轉載于:https://www.cnblogs.com/qixinbo/p/8297314.html
總結
以上是生活随笔為你收集整理的C#进阶之路(一):委托的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 巴克莱:对冲基金AI和大数据工作指南
- 下一篇: des加密解密JAVA与.NET互通实例