C#进阶之路(一):委托
一、什么是委托
簡(jiǎn)單說(shuō)它就是一個(gè)能把方法當(dāng)參數(shù)傳遞的對(duì)象,而且還知道怎么調(diào)用這個(gè)方法,同時(shí)也是粒度更小的“接口”(約束了指向方法的簽名)。
委托是一個(gè)類,它定義了方法的類型,使得可以將方法當(dāng)作另一個(gè)方法的參數(shù)來(lái)進(jìn)行傳遞,是種將方法動(dòng)態(tài)地賦給參數(shù)的做法。
用過(guò)C/C++的,對(duì)委托不會(huì)陌生,委托可以看成函數(shù)指針的升級(jí)版本!
函數(shù)指針簡(jiǎn)介:
下面是一段C程序,Calc就是定義的函數(shù)指針。
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); }這段程序很好的體現(xiàn)了一切皆地址的思想,變量和函數(shù)都是地址。
直接調(diào)用和間接調(diào)用的效果是一致的,都是訪問(wèn)那個(gè)內(nèi)存地址,委托相當(dāng)于函數(shù)指針的升級(jí)版。
委托的簡(jiǎn)單案例
一個(gè)委托類型定義了該類型的實(shí)例能調(diào)用的一類方法,這些方法含有同樣的返回類型和同樣參數(shù)(類型和個(gè)數(shù)相同)。
委托是一個(gè)類,所以要在類聲明的位置進(jìn)行聲明,而不是寫在類里面,那樣就寫成嵌套類了。如下定義了一個(gè)委托類型 - Calculator:
delegate int Calculator (int x);
此委托適用于任何有著int返回類型和一個(gè)int類型參數(shù)的方法,如:
static int Double (int x) { return x * 2; }
創(chuàng)建一個(gè)委托實(shí)例,將該此方法賦值給該委托實(shí)例:
Calculator c = new Calculator(Double);
也可以簡(jiǎn)寫成:
Calculator c = Double;
這個(gè)方法可以通過(guò)委托調(diào)用:
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 就是委托實(shí)例,int result = c(2);Console.Write(result);Console.ReadKey();} }二、委托的一般使用
2.1用委托實(shí)現(xiàn)插件式編程
我們可以利用“委托是一個(gè)能把方法作為參數(shù)傳遞的對(duì)象”這一特點(diǎn),來(lái)實(shí)現(xiàn)一種插件式編程。
例如,我們有一個(gè)Utility類,這個(gè)類實(shí)現(xiàn)一個(gè)通用方法(Calculate),用來(lái)執(zhí)行任何有一個(gè)整型參數(shù)和整型返回值的方法。這樣說(shuō)有點(diǎn)抽象,下面來(lái)看一個(gè)例子:
delegate int Calculator(int x);//這里定義了一個(gè)委托 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 是簡(jiǎn)單委托的變種寫法,就是把實(shí)例化放在了形參定義的語(yǔ)句里 //但是這個(gè)實(shí)例化具體對(duì)應(yīng)的是什么方法,只有真的傳入?yún)?shù)的時(shí)候才知道!for (int i = 0; i < values.Length; i++)values[i] = c(values[i]);} }這個(gè)例子中的Utility是固定不變的,程序?qū)崿F(xiàn)了整數(shù)的Double功能。我們可以把這個(gè)Double方法看作是一個(gè)插件,如果將來(lái)還要實(shí)現(xiàn)諸如求平方、求立方的計(jì)算,我們只需向程序中不斷添加插件就可以了。
如果Double方法是臨時(shí)的,只調(diào)用一次,若在整個(gè)程序中不會(huì)有第二次調(diào)用,那么我們可以在Main方法中更簡(jiǎn)潔更靈活的使用這種插件式編程,無(wú)需先定義方法,使用λ表達(dá)式即可,如:
...
Utility.Calculate(values, x => x * 2);
...
2.2多播委托
一個(gè)委托實(shí)例不僅可以指向一個(gè)方法,還可以指向多個(gè)方法。例如:
MyDelegate d = MyMethod1; // “+=” 用來(lái)添加,同理“-=”用來(lái)移除。 d += MyMethod2; // d -= MyMethod2調(diào)用時(shí),按照方法被添加的順序依次執(zhí)行。注意,對(duì)于委托,+= 和 -= 對(duì)null是不會(huì)報(bào)錯(cuò)的,如:
MyDelegate d; d += MyMethod1;// 相當(dāng)于MyDelegate d = MyMethod1;為了更好的理解多播在實(shí)際開(kāi)發(fā)中的應(yīng)用,我用模擬瞬聘網(wǎng)的職位匹配小工具來(lái)做示例。在職位匹配過(guò)程中會(huì)有一段處理時(shí)間,所以在執(zhí)行匹配的時(shí)候要能看到執(zhí)行的進(jìn)度,而且還要把執(zhí)行的進(jìn)度和執(zhí)行情況寫到日志文件中。在處理完一個(gè)步驟時(shí),將分別執(zhí)行兩個(gè)方法來(lái)顯示和記錄執(zhí)行進(jìn)度。
我們先定義一個(gè)委托(ProgressReporter),然后定義一個(gè)匹配方法(Match)來(lái)執(zhí)行該委托中的所有方法。如下:
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之后再繼續(xù)運(yùn)行程序! }}} }然后我們需要兩個(gè)監(jiān)視進(jìn)度的方法,一個(gè)把進(jìn)度寫到Console,另一個(gè)把進(jìn)度寫到文件。如下:
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 + "%");}}運(yùn)行結(jié)果:
?
看到這里,是不是發(fā)現(xiàn)你已然更加愛(ài)上C#了。
2.3靜態(tài)方法和實(shí)例方法對(duì)于委托的區(qū)別
當(dāng)一個(gè)類的實(shí)例的方法被賦給一個(gè)委托對(duì)象時(shí),在上下文中不僅要維護(hù)這個(gè)方法,還要維護(hù)這個(gè)方法所在的實(shí)例。System.Delegate 類的Target屬性指向的就是這個(gè)實(shí)例。(也就是要在內(nèi)存中維護(hù)這個(gè)實(shí)例,也就是可能的內(nèi)存泄漏)
但對(duì)于靜態(tài)方法,System.Delegate 類的Target屬性是Null,所以將靜態(tài)方法賦值給委托時(shí)性能更優(yōu)。
2.4泛型委托
如果你知道泛型,那么就很容易理解泛型委托,說(shuō)白了就是含有泛型參數(shù)的委托,例如:
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 委托
有了泛型委托,就有了能適用于任何返回類型和任意參數(shù)(類型和合理的個(gè)數(shù))的通用委托,Func 和 Action。如下所示(下面的in表示參數(shù),out表示返回結(jié)果):
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泛型委托就可以刪掉了,示例就可以更簡(jiǎn)潔了:
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 是對(duì)delegate的一種簡(jiǎn)寫,更簡(jiǎn)潔Func 和 Action 委托,除了ref參數(shù)和out參數(shù),基本上能適用于任何泛型委托的場(chǎng)景,非常好用。ACTION 和FUNC 最常用的兩種委托,類庫(kù)為我們準(zhǔn)備好的!
action就是一種委托的簡(jiǎn)便寫法,默認(rèn)的是無(wú)返回值類型的方法,注意不要加括號(hào),只是綁定地址,而不是執(zhí)行!Func這種用來(lái)調(diào)用有返回值的委托!
直接調(diào)用方法,使用calculator.report();
間接調(diào)用,使用action.Invoke();
action();這種寫法是為了模仿函數(shù)指針的寫法。實(shí)際上還是默認(rèn)調(diào)用invoke()
上面的案例寫的有些亂,而且是一種把func寫入函數(shù)參數(shù)的類型,不容易理解,下面再用另一個(gè)案例
static void Main(string[] args) {Func<string> RetBook = new Func<string>(FuncBook);Console.WriteLine(RetBook()); } public static string FuncBook() {return "送書(shū)來(lái)了"; }無(wú)返回值
static void Main(string[] args) {Action<string> BookAction = new Action<string>(Book);BookAction("百年孤獨(dú)"); } public static void Book(string BookName) {Console.WriteLine("我是買書(shū)的是:{0}",BookName); }2.6委托的異步調(diào)用
1、顯式異步調(diào)用
顯式異步調(diào)用 thread ?或者 task
?
2、隱式異步調(diào)用
使用委托進(jìn)行隱式異步調(diào)用,begininvoke就是隱式異步調(diào)用,它會(huì)開(kāi)發(fā)分支線程,他有兩個(gè)參數(shù)。
aciont1.BeginInvoke(null, null);
EndInvoke
隱式調(diào)用也有兩種,一種是不使用回調(diào)函數(shù)的,另一種是使用的。
不使用回調(diào)函數(shù)
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("開(kāi)始計(jì)算:" + a + "+" + b);Thread.Sleep(3000); //模擬該方法運(yùn)行三秒Console.WriteLine("計(jì)算完成!");return a + b;}}static void Main(string[] args){Console.WriteLine("===== 異步調(diào)用 AsyncInvokeTest =====");//AddHandler handler = new AddHandler(Cal.Add);//IAsyncResult: 異步操作接口(interface)//BeginInvoke: 委托(delegate)的一個(gè)異步方法的開(kāi)始//IAsyncResult result = handler.BeginInvoke(1, 2, null, null);Console.WriteLine("繼續(xù)做別的事情。。。");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使用回調(diào)函數(shù)
namespace CallBack { public delegate int AddHandler(int a, int b);public class Cal{public static int Add(int a, int b){Console.WriteLine("開(kāi)始計(jì)算:" + a + "+" + b);Thread.Sleep(3000); //模擬該方法運(yùn)行三秒Console.WriteLine("計(jì)算完成!");return a + b;}}class Program{static void Main(){Console.WriteLine("===== 異步回調(diào) AsyncInvokeTest =====");AddHandler handler = new AddHandler(Cal.Add);//異步操作接口(注意BeginInvoke方法的不同!)IAsyncResult result = handler.BeginInvoke(1, 2, new AsyncCallback(回調(diào)函數(shù)), "AsycState:OK");Console.WriteLine("繼續(xù)做別的事情。。。");Console.ReadKey();}static void 回調(diào)函數(shù)(IAsyncResult result){//result 是“加法類.Add()方法”的返回值//AsyncResult 是IAsyncResult接口的一個(gè)實(shí)現(xiàn)類,空間:System.Runtime.Remoting.Messaging//AsyncDelegate 屬性可以強(qiáng)制轉(zhuǎn)換為用戶定義的委托的實(shí)際類。AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;Console.WriteLine(handler.EndInvoke(result));Console.WriteLine(result.AsyncState);}} } View Code三、委托的缺點(diǎn)
?
引用了某個(gè)方法,那么這個(gè)方法在內(nèi)存中就不能釋放了,一旦釋放,委托就不能調(diào)用這個(gè)方法,所以委托有可能造成內(nèi)存泄漏。(靜態(tài)方法不存在這個(gè)問(wèn)題)
?
轉(zhuǎn)載于:https://www.cnblogs.com/qixinbo/p/8297314.html
總結(jié)
以上是生活随笔為你收集整理的C#进阶之路(一):委托的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 巴克莱:对冲基金AI和大数据工作指南
- 下一篇: c# char unsigned_dll