相信正在學習C#的人都有學習過C或C++的經驗,本文要講的第一個要點是C#中的委托(delegate,有些資料也叫代表)。什么是委托,很多人都能自然而然地想到C/C++中的函數指針,事實上很多書和資料都以此來引出C#中委托的概念,在此我建議如果沒有接觸過C/C++的同學可以先了解一下相關的知識再來繼續C#的學習,畢竟作為編程語言的基礎,語言都是招式,思維想法才是內功。有了扎實的基礎,后期學習起來才能夠事半功倍。
首先我們通過一個簡單的例子快速復習一下C/C++函數指針:
1 #include<iostream>
2 using namespace std; 3 int func(string name){ 4 cout<<"My name is "<<name<<endl; 5 } 6 void call(int(*fun)(string)){ 7 fun("Evan Lin"); 8 } 9 int main(int args,char ** argv){ 10 call(func); 11 }
重點是 int(*fun)(string) 這個語句,指定一個函數指針的形參,就如同我們定義一個變量 char ch 一樣,但要求是此處函數指針的返回值和參數列表都必須與即將傳進來的函數地址嚴格匹配,不然會產生[Invalid Conversion Error],且此處的聲明方式只能用指針,即這個 (*fun) ,因為事實上函數指針只是通過一個函數的入口去操作一個函數,雖然可以通過 typedef int (fun)(string); fun *fp 的方式去表示一個函數,但最終也是要定義一個函數的指針,所以此處無法不通過指針而去調用一個函數,至少目前階段我沒有了解到,有了解的朋友可以說出來共同探討。
C#中的委托和C/C++中函數指針的對比
1、C/C++函數指針是通過尋找函數的入口來調用一個函數,C#委托是把函數名當做一個參數傳入一個委托對象當中,委托是類型,函數指針是指針。
2、C/C++函數指針的返回類型和參數列表是作為匹配函數參數的標志,而C#委托有簽名(Signature)的概念。
3、C/C++函數指針直接操作內存的某個地址,而C#委托托管在.Net Framwork下,是一種強類型
委托的簽名(Signature)由委托的返回類型和參數列表組成,看起來和C/C++函數指針的返回類型和參數列表並無多大區別,但作為一門強大的語言,委托的簽名的作用不僅僅是作為一種限定作用,其他作用會由下文提及。下面我們按部就班地一步步來認識委托(delegate)
一、下面是一則例子用於介紹委托的使用方法
1 using System; 2 namespace ConsoleApplication { 3 class DelegateTest { 4 public delegate void myDelegate(string name); 5 public static void func(string name) { 6 Console.WriteLine("My name is " + name); 7 } 8 static void Main() { 9 myDelegate _myDe = new myDelegate(DelegateTest.func); 10 _myDe("Evan Lin"); 11 } 12 } 13 }
使用關鍵字delegate聲明一個委托類型,聲明形式主要是【delegate + 返回類型 + 委托名 + 參數列表】
像普通類型一樣定義一個委托變量,生成委托對象時必須把簽名相應的函數作為參數傳入委托對象當中,然后進行調用。
二、委托的快捷語法,可以直接把函數名賦值給委托變量
1 myDelegate _myDe = DelegateTest.func; 2 _myDe("Evan Lin");
委托和函數(與簽名相應)之間存在着隱式轉換
三、多播(Multicast)委托
多播委托表示可以通過+=和-=的運算符號來添加或者刪除到委托隊列當中,當執行這個委托的時候會按依次執行添加到委托隊列當中的所有委托,當使用多播委托時,委托的返回類型必須為void,否則運行時只會執行最后一個添加到委托隊列的委托。
此處需要注意一點,當添加兩個相同的函數時,在委托隊列當中實質上添加了兩個委托,但當減去一個委托時,如果該委托實體在委托隊列中存在時,則把這份委托刪除,但如果該委托實體在委托隊列中不存在時,委托隊列不做任何改變,且不會發生編譯時異常。當委托隊列為空,然后執行這個多播委托時,會拋出NullReferenceException。
下面看一小段代碼加以理解:
1 using System; 2 namespace ConsoleApplication1 { 3 class DelegateTest { 4 public delegate void AnimalDelegate(); 5 public static void Cat() { 6 Console.WriteLine("Miao Miao"); 7 } 8 public static void Dog() { 9 Console.WriteLine("Wang Wang"); 10 } 11 static void Main() { 12 AnimalDelegate _aniD; 13 AnimalDelegate _catD = new AnimalDelegate(DelegateTest.Cat); 14 AnimalDelegate _dogD = new AnimalDelegate(DelegateTest.Dog); 15 _aniD = _catD + _dogD; 16 _aniD();//Miao Miao \n Wang Wang 17 _aniD -= _catD; 18 _aniD();//Wang Wang 19 } 20 } 21 }
運行會依次打印“Miao Miao”和“Wang Wang”兩行結果,然后再打印“Wang Wang”。
四、匿名方法和Lambda表達式
可以注意到使用委托真正起到作用的僅僅是委托的簽名,為了提高開發效率,於是有了匿名方法(= =純屬猜想,歡迎斧正),具體實現方法如下:
1 using System; 2 namespace ConsoleApplication1 { 3 class DelegateTest { 4 public delegate String MyDelegate(int arg); 5 static void Main() { 6 MyDelegate _myDe = delegate (int arg) { 7 return arg > 0 ? "More than zero" : "Less than or equals zero"; 8 }; 9 Console.WriteLine(_myDe(0)); 10 Console.WriteLine(_myDe(1)); 11 } 12 } 13 }
如代碼所示,用【delegate關鍵字+參數列表+方法體】構成一個委托匿名方法,此處隱藏了具體的函數名稱,匿名方法的返回值可有可無(根據委托簽名),函數體的反花括號后要加分號,然后使用正常方法調用委托。說到這里相信大家都可以猜想到實際的輸出結果,分別是Less than or equals zero和More than zero兩行結果。
Lambda表達式具有比較特殊的寫法,同樣是為了提高開發效率,降低函數名的重復率等原因,以下通過一個實例進行了解:
1 using System; 2 namespace ConsoleApplication { 3 class DelegateTest { 4 public delegate String MyDelegate(int arg); 5 static void Main() { 6 MyDelegate _myDe = (arg) => { 7 return arg > 0 ? "More than zero" : "Less than or equals zero"; 8 }; 9 } 10 } 11 }
實際效果等同於上一個匿名方法,在Lambda表達式中連參數類型都省去了,因為在定義一個委托類型的時候已經限定了委托的參數類型,以以上代碼為例,其中參數arg的類型必須是int,返回類型必須是String。
五、委托泛型
如果對應於不同的函數返回類型和函數參數列表,需要聲明大量不同簽名的委托。泛型委托的出現是為了能適應不同類型的函數,提高代碼的復用率,以下通過一個簡單的例子來加深理解。
1 using System; 2 namespace ConsoleApplication { 3 class DelegateTest { 4 public delegate T1 myDelegate<T1, T2>(T1 arg1, T2 arg2); 5 public static string func1(string name,int num) { 6 return "My name is " + name + ",and my favorite number is " + num; 7 } 8 static void Main() { 9 myDelegate<string, int> _myDe = func1; 10 Console.WriteLine(_myDe("Evan Lin",13)); 11 } 12 } 13 }
其中的<T1,T2>代表兩種自定義類型,同時分別作為委托的兩種類型的參數,並且該委托返回T1類型的返回值。通過 myDelegate<string, int> _myDe 來限定<T1,T2>的具體類型。
當想定義一個泛型委托,但又想在類型方面做一些限制,可以用到where關鍵字
泛型委托約束大約包括幾種形式:
1 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA 2 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA,InterfaceA 3 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA where T2:ClassB 4 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:T2
where后面的表達式代表類型T1只能派生於ClassA類或者是ClassA本身,T2同理。而ClassA,ClassB,InterfaceA等也有一些限制條件,官方文檔的解釋是:A type used as a constraint must be an interface,a non-sealed class or a type parameter。也就是說作為限制條件的只能是某個接口,非密封(non-sealed)類或者某個參數的類型(即第四句語句所示),除此之外的類型都不能作為泛型約束的類型,否則回顯示Invalid constraint錯誤。
至此,以上均是個人學習C#委托時候的拙見,難免會有紕漏和不妥之處,歡迎指出斧正。
