最近在看深入理解C#,發現這是一本很不錯的書,將很多C#的知識點聯系了起來,更像是一本C#歷史書,從C# 1一步步介紹到C# 4。
所以准備一邊看,一邊整理讀書筆記。那麼就先從委托開始。
委托是C#中一個非常重要的概念,從C# 1開始就有了委托這個核心概念,在C# 2和C# 3中委托又有了很多改進。
通過委托,我們可以將一個方法當作對象封裝起來,並且在運行時,我們可以通過這個對象來完成方法的調用。
首先,來個簡單的例子,蘋果只負責設計iphone,而把組裝iphone的工作委托給富士康做。
class Apple { //聲明委托類型 public delegate void AssembleIphoneHandler(); public AssembleIphoneHandler AssembleIphone; public void DesignIphone() { Console.WriteLine("Design Iphone By Apple"); } } class Foxconn { //與委托類型簽名相同的方法 public void AssembleIphone() { Console.WriteLine("Assemble Iphone By Foxconn"); } } class Program { static void Main(string[] args) { Apple apple = new Apple(); Foxconn foxconn = new Foxconn(); //創建委托實例 apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone); apple.DesignIphone(); //委托實例的調用 apple.AssembleIphone(); //通過Invoke進行顯示調用 //apple.AssembleIphone.Invoke(); Console.Read(); } }
從上面的例子中,可以體會一下委托的使用。使用委托需要滿足4個條件:
當我們使用委托的時候,一定要注意這兩個概念。
委托類型,是通過delegate關鍵字聲明的一種類型,例如上面例子中的:
public delegate void AssembleIphoneHandler();
注意,"AssembleIphoneHandler"是一個委托類型,它有自己的方法,可以創建相關的實例。通過"ILSpy"可以看到"AssembleIphoneHandler"的方法以及父類信息。
委托類型的聲明過程中描述了該委托類型的簽名(返回類型,參數列表),這個簽名就決定了那個方法可以用來創建一個改委托類型的委托實例;同時,這個簽名還表示了該委托實例調用的簽名。
而委托實例,就是通過委托類型進行實例化的對象,例如上面例子中的:
apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone);
在創建委托實例的過程中,我們需要找到一個跟委托類型簽名相同的方法來完成委托實例的創建。
在前面的例子中,委托實例(apple.AssembleIphone)只對應一個操作(方法foxconn.AssembleIphone)。但是,每個委托實例都有一個操作列表,稱為委托實例的調用列表(invocation list)。在System.Delegate類中,有兩個靜態方法Combine和Remove,通過這兩個靜態方法,我們就可以進行兩個委托實例的調用列表的合並和刪除。
接著上面的例子進行修改,這次來看看委托的合並和刪除。
class Foxconn { //與委托類型簽名相同的方法 public void AssembleIphone() { Console.WriteLine("Assemble Iphone By Foxconn"); } public void PackIphone() { Console.WriteLine("Pack Ipnone By Foxconn"); } public void ShipIphone() { Console.WriteLine("Ship Iphone By Foxconn"); } } class Program { static void Main(string[] args) { Apple apple = new Apple(); Foxconn foxconn = new Foxconn(); //創建委托實例 apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone); //apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone); apple.AssembleIphone = (Apple.AssembleIphoneHandler)Delegate.Combine(apple.AssembleIphone, new Apple.AssembleIphoneHandler(foxconn.PackIphone)); apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.ShipIphone); apple.DesignIphone(); //委托實例的調用 apple.AssembleIphone(); //通過Invoke進行顯示調用 //apple.AssembleIphone.Invoke(); Console.Read(); } }
這次,我們在Foxconn類中加了打包和運輸的方法,這樣,可以通過Combine方法將組裝、打包和運輸三個操作合並到委托實例apple.AssembleIphone的調用列表中。
當我們調用委托實例的時候,委托實例的調用列表中的所有操作會依次被執行。
注意,一般在代碼中,很少直接使用Combine和Remove方法的顯式調用,而是通過"+="和"-="操作符來實現。
這裡有一點要提的是,委托是不易變的,一旦創建了一個委托實例後,這個實例的所有內容都不能被改變了(就像string一樣,string也是不易變的)。
所以說Combine和Remove都沒有改變委托實例,都是新建了一個委托實例。
在使用調用列表的時候,有些關鍵點需要注意一下,假如說一個委托實例的調用列表為[methodA, methodB, methodC]。那麼當我們調用委托實例的時候,methodA, methodB, methodC會依次被執行。
舉例,我們在PackIphone方法中加入一個異常,那麼委托列表中的ShipIphone操作將不會被執行到:
class Foxconn { //與委托類型簽名相同的方法 public void AssembleIphone() { Console.WriteLine("Assemble Iphone By Foxconn"); } public void PackIphone() { throw new NotImplementedException(); } public void ShipIphone() { Console.WriteLine("Ship Iphone By Foxconn"); } } class Program { static void Main(string[] args) { Apple apple = new Apple(); Foxconn foxconn = new Foxconn(); //創建委托實例 apple.AssembleIphone = new Apple.AssembleIphoneHandler(foxconn.AssembleIphone); //apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.PackIphone); apple.AssembleIphone = (Apple.AssembleIphoneHandler)Delegate.Combine(apple.AssembleIphone, new Apple.AssembleIphoneHandler(foxconn.PackIphone)); apple.AssembleIphone += new Apple.AssembleIphoneHandler(foxconn.ShipIphone); apple.DesignIphone(); //委托實例的調用 try { apple.AssembleIphone(); } catch { Console.WriteLine("an exception happened"); } Console.Read(); } }
對於上面兩個問題,我們可以通過委托實例的GetInvocationList()方法,通過這個方法可以得到調用列表中的所有操作。
這樣,就可以顯示調用委托來進行異常處理或者返回值的保存。
foreach (Apple.AssembleIphoneHandler method in apple.AssembleIphone.GetInvocationList()) { try { method(); } catch { Console.WriteLine("an exception happened"); } }
本文介紹了委托的基本概念,以及委托類型和委托實例的區別。
委托本質上是一個派生自System.MulticastDelegate的類,我們可以通過特定的(與委托類型簽名相同)的方法創建委托實例,通過委托是,可以間接完成某些操作。
同時,可以通過Combine和Remove操作來進行委托實例的調用列表的合並和刪除。