本篇文章主要介紹委托的應用。 委托是大家最常見的語法了,但會用與精通之間的差距是巨大的。 一個程式員如果不能精通委托,那麼,他永遠無法成為高級程式員。 所以,讓我們把委托刻到血液里吧。 這樣,你才能稱為Developer。 ...
本篇文章主要介紹委托的應用。
委托是大家最常見的語法了,但會用與精通之間的差別是巨大的。
一個程式員如果不能精通委托,那麼,他永遠無法成為高級程式員。
所以,讓我們把委托刻到血液里吧。
這樣,你才能稱為[Developer]。
委托的定義
什麼是委托?
委托實際上是一種類型,是一種引用類型。
微軟用delegate關鍵字來聲明委托,delegate與int,string,double等關鍵字一樣。都是聲明用的。
下麵先看下聲明代碼,這裡聲明瞭兩個委托。
public delegate void TestDelegate(string message); public delegate int TestDelegate(MyType m, long num);
delegate既然是關鍵字,和int,string一樣,那麼,為什麼delegate後又跟了一個void或者int呢?
如果他們是同等地位的關鍵字,為什麼可以一起使用呢?
很簡單,我們把delegate後面的 【void TestDelegate(string message)】理解為一個變數,是不是就清晰明瞭了一些。
我們把delegate關鍵字理解為,是用來專門來定義這種複雜的變數的。而這種複雜的變數可以包含一個返回值和任意數目任意類型的傳入參數。
有沒有感覺,這個複雜的變數特別像一個函數的定義。
沒錯,官方定義,委托類型的聲明與方法簽名相似。所以,這個複雜變數,的確,書寫的方式就是與函數一樣。
那麼,為什麼這個聲明方式如此怪異呢,是因為,我們用delegate定義的變數,只能用函數賦值。賦值方式如下所示:
public delegate void TestDelegate(string message); public delegate long TestDelegate2(int m, long num); public static void Excute() { TestDelegate2 td = Double; } static long Double(int m, long num) { return m * num; }
委托的基本應用
學會了賦值以後,我開始使用委托。
委托的使用方式如下:
string result = td(51, 8); Console.WriteLine(result);
這裡我們會發現,委托的使用方式與函數調用一樣。
沒錯,它們的確是一樣的。因為委托是用函數來賦值的,所以調用方式一樣也並不奇怪,不是嗎。
換一種說法,就是委托封裝了一個函數。
如果委托是封裝的函數,並且它又是引用類型。那麼委托第一種常規的應用就浮現出來了。
那就是——引用類型的函數。
如果函數是引用類型,那麼這個函數只要沒被記憶體回收,就可以被調用。如果是public函數或者是public static函數,那麼它能跨越的東西就更多了。
比如可以跨類調用,跨程式集調用等等。而這種用法,就是委托的基本應用。
匿名委托的應用
匿名委托的官方介紹:在 2.0 之前的 C# 版本中,聲明委托的唯一方式是使用命名方法。 C# 2.0 引入匿名方法,在 C# 3.0 及更高版本中,Lambda 表達式取代匿名方法作為編寫內聯代碼的首選方式。
看不懂沒關係,我們直接來學習使用。代碼如下:
delegate string anonymousDelegate(int m, long num); public static void Excute() { anonymousDelegate ad = delegate (int m, long num) { return m.ToString() + num.ToString(); };//2.0時代的匿名委托 anonymousDelegate ad2 = (m, num) => { return m.ToString() + num.ToString(); };//3.0以後匿名委托 }
如代碼所示,匿名委托是Lambda表達式,不懂的同學就當它是有固定寫法即可,不用講什麼道理,只要記住並應用即可。
匿名委托雖然減少了一點代碼,但還是要求我們自己去聲明委托。所有,還能再簡寫一點嗎?
答案當然是,可以的。
Action與Func
Action與Func是微軟為我們預先定義好了的,兩個委托變數。其中Action是不帶返回值的委托,Func是帶返回值的委托。
可以說,Action與Func完全包含了,我們日常使用所需的,全部的,委托變數。
也就是說,我們可以不用再去自己手動聲明委托了。
下麵來看最簡單的Action與Func的定義:
Action a1 = () => { }; Func<int> f1 = () => { return 1; };//必須寫 return 1;
Action與Func是泛型委托,各支持16個入參變數。下麵代碼為一個入參的定義,多參數以此類推。
Action<int> a1 = (i) => { }; Func<string,int> f1 = (str) => { return 1;//必須寫 return 1; };
委托的線程應用
委托的線程應用是委托的第二種用法,分為線程使用委托,和委托的非同步應用兩種。
我們先看線程使用委托。如下代碼所示,一個無入參匿名Action和一個無入參匿名Func。
Task taskAction = new Task(() => { });//無入參匿名Action taskAction.Start(); Task<int> taskFunc = new Task<int>(() => { return 1; });//無入參匿名Func taskFunc.Start(); int result= taskFunc.GetAwaiter().GetResult();//獲取線程返回結果
我們能看到兩種委托應用,代碼都非常簡潔。
下麵我們再來看委托的非同步應用。首先看最簡單的非同步調用。
Action action = new Action(() => { }); IAsyncResult result = action.BeginInvoke((iar) => { }, null); Func<int> func = new Func<int>(() => { return 1; }); IAsyncResult resultfunc = func.BeginInvoke((iar) => { var res = func.EndInvoke(iar); }, null);
這裡我們使用委托的BeginInvoke方法來開啟線程,進行非同步調用。如上面代碼所示,這裡介紹了Action與Func的最基礎的非同步應用。
委托,架構的血液
委托是架構的血液,如果系統中沒有委托,那代碼將堆疊到一起,比大力膠粘的都緊密。
就好比一碗湯麵倒掉了所有的湯,只要它靜放一個陣子,就會變成一坨面球,讓你無從下嘴。
所以,委托是架構的血液,是框架的流暢的基石。
那麼委托到底是如何流動的呢?
我們先從剛介紹過的委托的線程應用說起。
----------------------------------------------------------------------------------------------------
第一核心應用——隨手線程:
我們在做開發的時候,一定接觸過父類。父類是乾什麼的呢?父類通常是用來編寫公共屬性和函數,方便子類調用的。
那我們的委托的第一個核心應用,就是父類的公共函數,線程隨手啟動。如何隨手開啟呢?
首先,我們創建父類代碼如下:
class BaseDelegateSyntax { public void AsyncLoad(Action action) { } public void AsyncLoad(Action action, Action callback) { IAsyncResult result = action.BeginInvoke((iar) => { callback(); }, null); } public void AsyncLoad<T>(Action<T> action, T para, Action callback) { IAsyncResult result = action.BeginInvoke(para, (iar) => { callback(); }, null); } public void AsyncLoad<T, R>(Func<T, R> action, T para, Action<R> callback) { IAsyncResult result = action.BeginInvoke(para, (iar) => { var res = action.EndInvoke(iar); callback(res); }, null); } }
我們看到上面的代碼,父類中添加了四個非同步委托的調用函數,接下來,我們就可以在繼承該類的子類中,隨手開啟線程了。
子類代碼如下:
class ChildDelegateSyntax : BaseDelegateSyntax { public void Excute() { //開啟非同步方法 base.AsyncLoad(() => { }); //開啟非同步方法,並且在非同步結束後,觸發回調方法 base.AsyncLoad(() => { }, ()=> { //我是回調方法 }); //開啟非同步有入參的方法,傳遞參數,並且在非同步結束後,觸發回調方法 base.AsyncLoad<string>((s) => { },"Kiba518", () => { //我是回調方法 }); //開啟非同步有入參的方法,傳遞字元串參數Kiba518,之後返回int型結果518, //並且在非同步結束後,觸發回調方法,回調函數中可以獲得結果518 base.AsyncLoad<string,int>((s) => { return 518; }, "Kiba518", (result) => { //我是回調方法 result是返回值518 }); } }
看了上面的父子類後,是否感覺委托讓我們繁雜的線程世界變簡潔了呢?
----------------------------------------------------------------------------------------------------
第二核心應用——穿越你的世界:
接下來,我們來看委托的第二種核心用法,穿越的應用。
這個應用,是最常見,也最普通的應用了。因為委托是引用類型,所以A類里定義的委托,可以在被記憶體回收之前,被其他類調用。
我們經常會在各種論壇看到有人發問,A頁面如何調用B頁面的屬性、方法、父頁面獲取子頁面的屬性、方法,或者子頁面獲取父頁面的屬性、方法。
其實,只要定義好委托,並將委托正確的傳遞,就可以實現穿越的調用了。
下麵我們看下穿越應用的代碼。
public class FirstDelegateSyntax { public FirstDelegateSyntax() { Console.WriteLine(" First 開始 " ); SecondDelegateSyntax sds = new SecondDelegateSyntax(()=> { Console.WriteLine(" First傳給Second委托被觸發 "); }); sds.Excute(); Console.WriteLine(" First 結束 "); } } public class SecondDelegateSyntax { public Action Action { get; set; } public SecondDelegateSyntax(Action _action) { Console.WriteLine(" Second的構造函數 "); Action = _action; } public void Excute() { Console.WriteLine(" Second的Excute被觸發 "); Action(); } }
我們可以看到,我們傳遞的委托,穿越了自身所屬的類。在SecondDelegateSyntax類中被觸發了。
運行結果如下:
第三核心應用——回調函數:
世界上本沒有回調函數,叫的人多了,也就有了。
請記住,所有的回調函數,都是委托的穿越應用,所有的回調函數;都是委托的穿越應用;所有的回調函數,都是委托的穿越應用。
重要的話要講三遍。
因為委托是引用類型,所以可以被[址傳遞]。函數是不可以被傳遞的。
當你傳遞函數的時候,其實是匿名傳遞了一個委托的地址。
結語
委托是我們最常用的語法,它將函數封裝成引用類型的變數,供其他單位調用。
因為委托的特質是引用類型,所以決定了委托是可以進行址傳遞。也就是說,委托是穿梭於我們系統代碼中的列車。
我們可以在列車上放很多很多東西,在需要的站點,叫停列車,並將托運的東西搬下來使用。
所以,理論上,只要我們利用好委托,就可以大量減少冗餘的代碼。
但委托這種列車,是每個程式員都可以定義的,如果一個項目中有十個開發者,每個人都在定義委托,那麼,就有可能出現定義了十個相同的委托的情況,這樣就出現了撞車的現象。
所以委托在使用的時候,儘量做到有序傳遞,即預先做好列車的行駛路線,讓委托按照路徑運行。儘量不要定義可以被任何單位調用的公共委托。
如果需要公共委托,可以採取反射的方式來調用。
後面我會繼續寫事件,消息,反射等語法,敬請期待。
----------------------------------------------------------------------------------------------------
註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接!
若您覺得這篇文章還不錯,請點擊下右下角的【推薦】,非常感謝!
如果您覺得這篇文章對您有所幫助,那就不妨小小打賞一下吧。