前言
開篇先來扯下淡,上篇博客LZ在結尾說這篇博客會來說說C#中的事件。但是當LZ看完事件之后發現事件是以委托為基礎來實現的,於是LZ就自作主張地在這篇博客中先來說說委托,還煩請各位看官見諒!!!另外關於委托推薦一篇Jimmy Zhang寫的關於委托的博客(C# 中的委托和事件),敘述非常有條理,可見子陽兄的文筆不凡。
博客結構
- 加工廠問題
- 委托來提高加工廠效率
- 委托的更多用法
- 委托到底是什么
加工廠問題
假設現在我們開了一個電子設備外包工廠(就像里面有很多人跳樓的那某某康),專門負責為國際上的大公司代工生產電子設備產品。某天,加工廠收到了來自美國蘋果公司的訂單,蘋果公司委托加工廠為他們生產一批iPhone電話機,於是加工廠就像下面這么干,效果不錯還挺好,蘋果公司很滿意。
1 static void Main(string[] args) 2 { 3 DigitalFactory factory = new DigitalFactory(); 4 factory.MakeiPhone(200); 5 } 6 7 //摘要: 8 // 加工廠類 9 public class DigitalFactory 10 { 11 // 生產iPhone 12 public void MakeiPhone(Int32 Number) 13 { 14 Console.WriteLine("" + Number + "台iPhone已經成功生產出來了......");
15 Console.ReadKey(); 16 } 17 } 18 輸出: 19 200台iPhone已經成功生產出來了......
后來呢,蘋果公司覺得加工廠生產的iPhone質量很好,於是又委托加工廠為他們生產一批iPad平板電腦,於是加工廠又像下面這么干了。
1 static void Main(string[] args) 2 { 3 DigitalFactory factory = new DigitalFactory(); 4 factory.MakeiPhone(200); 5 factory.MakeiPad(200); 6 } 7 8 //摘要: 9 // 加工廠類 10 public class DigitalFactory 11 { 12 // 生產iPhone 13 public void MakeiPhone(Int32 Number) 14 { 15 Console.WriteLine("" + Number + "台iPhone已經成功生產出來了......"); 16 Console.Read(); 17 } 18 19 // 生產iPad 20 public void MakeiPad(Int32 Number) 21 { 22 Console.WriteLine("" + Number + "台iPad已經成功生產出來了......"); 23 Console.Read(); 24 } 25 } 26 輸出: 27 200台iPhone已經成功生產出來了...... 28 200台iPad已經成功生產出來了......
這次蘋果公司更滿意,又繼續給了代工生產iMac和iPod的兩個訂單,於是加工廠又傻乎乎地像下面這么干了。
1 static void Main(string[] args) 2 { 3 DigitalFactory factory = new DigitalFactory(); 4 factory.MakeiPhone(200); 5 factory.MakeiPad(200); 6 factory.MakeiMac(200); 7 factory.MakeiPod(200); 8 } 9 10 //摘要: 11 // 加工廠類 12 public class DigitalFactory 13 { 14 // 生產iPhone 15 public void MakeiPhone(Int32 Number) 16 { 17 Console.WriteLine("" + Number + "台iPhone已經成功生產出來了......"); 18 Console.Read(); 19 } 20 21 // 生產iPad 22 public void MakeiPad(Int32 Number) 23 { 24 Console.WriteLine("" + Number + "台iPad已經成功生產出來了......"); 25 Console.Read(); 26 } 27 28 // 生產iMac 29 public void MakeiMac(Int32 Number) 30 { 31 Console.WriteLine("" + Number + "台iMac已經成功生產出來了......"); 32 Console.Read(); 33 } 34 35 // 生產iPod 36 public void MakeiPod(Int32 Number) 37 { 38 Console.WriteLine("" + Number + "台iPod已經成功生產出來了......"); 39 Console.Read(); 40 } 41 } 42 輸出: 43 200台iPhone已經成功生產出來了...... 44 200台iPad已經成功生產出來了...... 45 200台iMac已經成功生產出來了...... 46 200台iPod已經成功生產出來了......
到現在加工廠才發現,隨着訂單越來越多,每代工生產一種產品就得新定義一個方法,那樣效率太低了已經忙不過來了。所以,加工廠采用了一種新的生產模式來提高效率。
委托來提高加工廠效率
前面說到加工廠發現傻傻地為每一種代工產品提供一個發放的效率實在是太低下了,於是采用了一種新的模式。沒錯,大家可能都已經猜到了,這種新模式就是委托,讓我們一起來看一下這新模式是如何提高效率的。
1 //摘要: 2 // 定義一個委托類型 3 public delegate void Dele(Int32 Number); 4 5 static void Main(string[] args) 6 { 7 DigitalFactory factory = new DigitalFactory(); 8 //將生產iPhone的方法傳遞給生產電子設備的方法 9 factory.MakeDigitals(new Apple().MakeiPhone, 200); 10 //將生產iPad的方法傳遞給生產電子設備的方法 11 factory.MakeDigitals(new Apple().MakeiPad, 200); 12 //將生產iMac的方法傳遞給生產電子設備的方法 13 factory.MakeDigitals(new Apple().MakeiMac, 200); 14 //將生產iPod的方法傳遞給生產電子設備的方法 15 factory.MakeDigitals(new Apple().MakeiPod, 200); 16 } 17 18 //摘要: 19 // 加工廠類 20 public class DigitalFactory 21 { 22 //定義一個通用的生產設備方法,接受一個委托變量和一個設備預生產數量作為參數 23 public void MakeDigitals(Dele dele, Int32 Number) 24 { 25 //判斷委托對象是否為空,非空才執行 26 if (dele != null) 27 { 28 dele(Number); 29 } 30 } 31 } 32 33 //摘要: 34 // 蘋果公司類 35 public class Apple 36 { 37 // 生產iPhone 38 public void MakeiPhone(Int32 Number) 39 { 40 Console.WriteLine("" + Number + "台iPhone已經成功生產出來了......"); 41 Console.Read(); 42 } 43 44 // 生產iPad 45 public void MakeiPad(Int32 Number) 46 { 47 Console.WriteLine("" + Number + "台iPad已經成功生產出來了......"); 48 Console.Read(); 49 } 50 51 // 生產iMac 52 public void MakeiMac(Int32 Number) 53 { 54 Console.WriteLine("" + Number + "台iMac已經成功生產出來了......"); 55 Console.Read(); 56 } 57 58 // 生產iPod 59 public void MakeiPod(Int32 Number) 60 { 61 Console.WriteLine("" + Number + "台iPod已經成功生產出來了......"); 62 Console.Read(); 63 } 64 } 65 輸出: 66 200台iPhone已經成功生產出來了...... 67 200台iPad已經成功生產出來了...... 68 200台iMac已經成功生產出來了...... 69 200台iPod已經成功生產出來了......
加工廠現在定義了一個委托(上面代碼中的Dele),要求蘋果公司就是按照委托的要求將產品的設計、工藝、生產流程等和生產產品所有有關的細節全整理出來。加工廠現在就只有一個通用的產品生產方法(上面代碼中的MakeDigitals),加工廠不再關心電子設備的其他細節,只要按照蘋果公司給的生產設備的方法(上面代碼中的MakeiPhone,MakeiPad等)生產出產品即可。這樣,加工廠的效率是不是就提高多了?
委托往簡單的方面說,就是“把方法作為方法的參數傳遞給方法”。這句話是不是很繞口?意思就是假如方法A接受一個委托類型的參數,其他只要符合委托類型簽名的方法就都可以當做參數傳遞給方法A。在上面的代碼中,委托Dele簽名指定的方法要獲取一個Int32類型的參數,並且返回值為void。蘋果公司定義的四個方法MakeiPhone,MakeiPad,MakeiMac和MakeiPod的簽名都符合委托類型Dele指定的方法的簽名,所以這四個方法才能被傳遞給接受Dele類型作為參數的MakeDigitals方法。然后在MakieDigitals方法內部回調當做參數傳遞進來的方法,執行生產電子設備的邏輯。
讓我們來看一看c#中委托的定義:
1 //摘要: 2 // 定義一個委托類型 3 public delegate void Dele(Int32 Number);
C#通過delegate關鍵字來聲明委托,同時要指定委托的返回類型(此處為void)和參數(此處為Int32類型的Number),這樣委托就聲明好了。定義好的委托就相當於一個“類型”。下面就來看一下接受委托類型作為參數的方法的定義:
1 //定義一個通用的生產設備方法,接受一個委托變量和一個設備預生產數量作為參數 2 public void MakeDigitals(Dele dele, Int32 Number) 3 { 4 //判斷委托對象是否為空,非空才執行 5 if (dele != null) 6 { 7 dele(Number); 8 } 9 }
MakeDigitals方法接受一個Dele類型的委托和一個32位整數作為參數(把Dele想象成String就很好理解了,委托讓我們也過了一個定義“類型”的癮,哈哈)。只要符合Dele指定簽名的方法就都可以作為“參數”傳遞給MakeDigitals方法了。下面就是調用MakeDigitals方法的代碼:
1 DigitalFactory factory = new DigitalFactory(); 2 //將生產iPhone的方法傳遞給生產電子設備的方法 3 factory.MakeDigitals(new Apple().MakeiPhone, 200);
看到這是不是覺得委托很簡單呢?其實委托還可以幫助我們完成更多的事!
委托的更多用法
在加工廠的代碼中我們只是使用委托回調了實例方法,其實委托還可以回調靜態方法,還可以通過委托鏈一次性調用多個方法。下面我們就還是通過加工廠的代碼來試探一下委托這位兄台都有哪些本事!
- 委托調用靜態方法
加工廠的例子中我們全部回調的是實例方法,下面我們還是通過加工廠來調用一下靜態方法:
1 //摘要: 2 // 定義一個委托類型 3 public delegate void Dele(Int32 Number); 4 5 static void Main(string[] args) 6 { 7 DigitalFactory factory = new DigitalFactory(); 8 //將生產iPhone的方法傳遞給生產電子設備的方法,此時MakeiPhone為靜態方法 9 factory.MakeDigitals(Apple.MakeiPhone, 200); 10 } 11 12 //摘要: 13 // 加工廠類 14 public class DigitalFactory 15 { 16 //定義一個通用的生產設備方法,接受一個委托變量和一個設備預生產數量作為參數 17 public void MakeDigitals(Dele dele, Int32 Number) 18 { 19 //判斷委托對象是否為空,非空才執行 20 if (dele != null) 21 { 22 dele(Number); 23 } 24 } 25 } 26 27 //摘要: 28 // 蘋果公司類 29 public class Apple 30 { 31 // 生產iPhone,靜態方法 32 public static void MakeiPhone(Int32 Number) 33 { 34 Console.WriteLine("" + Number + "台iPhone已經成功生產出來了......"); 35 Console.Read(); 36 } 37 } 38 輸出: 39 200台iPhone已經成功生產出來了......
通過委托回調靜態方法和回調實例方法類似,按照傳統的靜態方法調用方式調用就行了。
- 通過委托鏈調用多個方法
CLR通過委托鏈在很大程度上幫助我們減少了新建委托類型對象的數量,只要是符合委托類型簽名規則的方法,都可以加入到委托類型實例的委托鏈中。CLR會在調用委托類型實例的代碼處循環去調用委托鏈 中的方法,我們一起來看一下委托鏈的使用:
1 //省略Dele、Apple和DigitalFactory的定義代碼 2 3 static void Main(string[] args) 4 { 5 DigitalFactory factory = new DigitalFactory(); 6 //新建一個Dele類型的委托對象,並且把MakeiPhone方法包裝到委托對象里面 7 Dele del1 = new Dele(Apple.MakeiPhone); 8 //將MakeiPad方法加入委托對象的委托鏈 9 del1 += new Apple().MakeiPad; 10 //將MakeiMac方法加入委托對象的委托鏈 11 del1 += new Apple().MakeiMac; 12 //將MakeiPod方法加入委托對象的委托鏈 13 del1 += new Apple().MakeiPod; 14 factory.MakeDigitals(del1, 200); 15 } 16 輸出: 17 200台iPhone已經成功生產出來了...... 18 200台iPad已經成功生產出來了...... 19 200台iMac已經成功生產出來了...... 20 200台iPod已經成功生產出來了......
可以看到在上面的代碼中,我們新建了一個Dele類型的委托對象(新建時包裝了對Apple.MakeiPhone方法的引用),然后通過 += 操作符將其余三個方法的引用加入到了del1的委托鏈中,最后通過和加工廠代碼相同的方式調用委托,輸出了同樣的結果。
- 委托與Lambda表達式
將符合委托類型方法簽名的方法定義好,然后再通過委托回調這些方法固然很有用,絕大多數時候我們也這么干,效果也還不錯。但是在某些情況下定義的方法就被回調一兩次,這太對不起我們辛辛苦苦地定 義方法了,Microsoft於是急我們之所急為我們提供了簡便方法,真是大好人吶!!!下面我們來看看大好人是怎么對我們好的:
1 //省略Dele、Apple和DigitalFactory的定義代碼 2 3 static void Main(string[] args) 4 { 5 DigitalFactory factory = new DigitalFactory(); 6 factory.MakeDigitals(obj => 7 { 8 Console.WriteLine(obj + "台iPhone已經成功生產出來了......"); 9 Console.ReadKey(); 10 }, 200); 11 } 12 輸出: 13 200台iPhone已經成功生產出來了......
上面我們就沒定義MakeiPhone()方法,把它的實現內聯進了代碼里面。=>的左邊就是方法需要的參數,如果方法需要多個參數就需要用括號括起來並且用逗號隔開。=>右邊是方法的主體,多行語句也需要用大括號括起來。如果委托預期一個返回值,直接在內聯代碼里面加入return語句就行了。
另外,在使用Lambda表達式內聯進代碼的方式時,Lambda表達式還可以訪問類的內部成員,像下面這樣:
1 //省略Dele、Apple和DigitalFactory的定義代碼 2 3 static void Main(string[] args) 4 { 5 String LambdaDesc = "這是使用Lambda表達式實現的委托調用:"; 6 DigitalFactory factory = new DigitalFactory(); 7 factory.MakeDigitals(obj => 8 { 9 Console.Write(LambdaDesc); 10 Console.WriteLine(obj + "台iPhone已經成功生產出來了......"); 11 Console.ReadKey(); 12 }, 200); 13 } 14 輸出: 15 這是使用Lambda表達式實現的委托調用:200台iPhone已經成功生產出來了......
在Lambda表達式的內部,我們訪問了屬於Main方法的LambdaDesc變量。值得注意的是此時的MakeDigitals是實例方法,如果MakeDigitals是靜態方法那么就只能訪問靜態成員,而不能訪問此處的LambdaDesc。
另外:FCL已經為我們定義了大部分一般情況下需要的委托類型,例如:System.Func<out TResult>、System.Predicate<in T>、System.Action<>等等。這些委托類型能夠滿足絕大部分我們日常編碼中的需要。
委托到底是什么
前面我們看過了委托的各種用法,那么委托為什么能夠回調方法?委托到底是怎么實現的呢?下面我們就來看看委托究竟是個什么東西!
首先,我們使用ILDasm.exe來查看生成的程序集,看看編譯器生成了什么IL,編譯器生成的IL如下:
通過上面的圖片我們看到,編譯器把Dele類型的委托編譯成了一個類,這個類繼承自System.MulticastDelete,同時它還有一個無返回值的構造函數,以及BeginInvoke、EndInvoke和Invoke三個方法。到這里就真相大白了,其實委托是一個類。它的完整定義就像下面這樣:
1 public class Dele : System.MulticastDelegate 2 { 3 //構造器 4 public Dele(Object object,IntPtr method); 5 6 public virtual void Invoke(Int32 value); 7 8 //以下兩個方法實現了對方法的回調 9 public virtual IAsyncResult BeginInvoke(Int32 value,AsyncCallback callback,Object object); 10 11 public virtual IAsyncResult EndInvoke(IAsyncResult result); 12 }
所有的委托類型都派生自System.MulticastDelegate,System.MulticastDelegate又派生自System.Delegate,而System.Delegate最終派生自所有類型的基類:System.Object。因為委托是類,所以只要能夠定義類的地方就都可以定義委托,下面我們先來看一下System.MulticastDelegate中三個非常重要的非公共字段_target、_methodPtr和_invocationList:
- _target:從定義就可以看出,這個字段引用的是回調方法要操作的對象,也就是定義回調方法的類型的實例,在加工廠的代碼里面,_target引用的就是Apple的實例。值得注意的是如果委托對象包裝的是一個靜態方法,那么_target就為null。
- _methodPtr:代表的是一個整數值,CLR用這個整數值來標識要回調的方法。
- _invocationList:引用的是一個委托數組,當委托對象只包裝了一個方法時,該字段的值為null。如果委托對象包裝了一組方法,該字段就引用一個委托數組,就是我們前面使用的委托鏈的實現。
因為上面的三個字段都是MulticastDelegate類的非公共字段,所以是不能訪問的,但是我們可以訪問Delegate的Target和Method屬性,功能和_target以及_methodPtr一樣。我們還是通過加工廠的代碼來看一下:
1 public class DigitalFactory 2 { 3 //定義一個通用的生產設備方法,接受一個委托變量和一個設備預生產數量作為參數 4 public void MakeDigitals(Dele dele, Int32 Number) 5 { 6 //判斷委托對象是否為空,非空才執行 7 if (dele != null) 8 { 9 dele.Invoke(Number); 10 Console.WriteLine("Target:" + dele.Target + ",Method:" + dele.Method.Name); 11 Console.ReadKey(); 12 } 13 } 14 } 15 輸出: 16 200台iPhone已經成功生產出來了...... 17 Target:AllChapters.Program+Apple,Method:MakeiPhone
上面的代碼中,我們在MakeDigitals方法里訪問了dele的Target屬性和Method屬性,並且成功把信息打印了出來。調用這兩個屬性來判斷委托對象包裝的方法所在的類型和包裝的方法在某些時候非常有用!!!
總結
怎么樣?現在對委托的用法和原理是不是有了一個重新的認識,在日常的代碼中委托確實是一個非常有用的利器,對簡化代碼和提高代碼重用率都有非常大的幫助。
最后希望我的文章能夠對你有所幫助,如果你覺得文章還不錯,麻煩請在右下角點一個推薦,謝謝!!!