C#編程語言之委託與事件(一)—— C/C++函數指針和C#委託初步

  相信正在學習C#的人都有學習過C或C++的經驗,本文要講的第一個要點是C#中的委託(delegate,有些資料也叫表明)。什麼是委託,不少人都能天然而然地想到C/C++中的函數指針,事實上不少書和資料都以此來引出C#中委託的概念,在此我建議若是沒有接觸過C/C++的同窗能夠先了解一下相關的知識再來繼續C#的學習,畢竟做爲編程語言的基礎,語言都是招式,思惟想法纔是內功。有了紮實的基礎,後期學習起來纔可以事半功倍。ios

  首先咱們經過一個簡單的例子快速複習一下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++中函數指針的對比函數

  一、C/C++函數指針是經過尋找函數的入口來調用一個函數,C#委託是把函數名當作一個參數傳入一個委託對象當中,委託是類型,函數指針是指針。學習

  二、C/C++函數指針的返回類型和參數列表是做爲匹配函數參數的標誌,而C#委託有簽名(Signature)的概念。spa

  三、C/C++函數指針直接操做內存的某個地址,而C#委託託管在.Net Framwork下,是一種強類型指針

  委託的簽名(Signature)由委託的返回類型和參數列表組成,看起來和C/C++函數指針的返回類型和參數列表並沒有多大區別,但做爲一門強大的語言,委託的簽名的做用不只僅是做爲一種限定做用,其餘做用會由下文說起。下面咱們循序漸進地一步步來認識委託(delegate)code

  1、下面是一則例子用於介紹委託的使用方法對象

 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 + 返回類型 + 委託名 + 參數列表】blog

  像普通類型同樣定義一個委託變量,生成委託對象時必須把簽名相應的函數做爲參數傳入委託對象當中,而後進行調用。

  2、委託的快捷語法,能夠直接把函數名賦值給委託變量

1 myDelegate _myDe = DelegateTest.func;
2 _myDe("Evan Lin");

  委託和函數(與簽名相應)之間存在着隱式轉換

  3、多播(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」。

  4、匿名方法和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。

  5、委託泛型

  若是對應於不一樣的函數返回類型和函數參數列表,須要聲明大量不一樣簽名的委託。泛型委託的出現是爲了能適應不一樣類型的函數,提升代碼的複用率,如下經過一個簡單的例子來加深理解。

 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#委託時候的拙見,不免會有紕漏和不妥之處,歡迎指出斧正。

相關文章
相關標籤/搜索