從一開始接觸C#到現在,委托對我來說都是一道坎,曾經想要避開,可是每次寫、看C#程序都避免不了接觸這玩意兒,每次都會覺得很憋屈!所以這幾天想下點苦功夫一下子徹底搞懂它,下面說說自己的見解吧~~
一、什么是委托對于這個詞,一開始接觸的時候很陌生,這也導致了我的畏懼心理,后來學習設計模式的時候知道了委托模型,當時也不怎么搞的明白,現在冷靜下來自己看看,這里也算入門了。委托,說白了就是一種
相當于c++函數指針的東東(如果對C++函數指針不熟的話這就等于白說了啊,還是看下面的例子吧?。﹡
首先看一下委托的額聲明例子: public void delegate Delete_Delegate(string args);委托的聲明中有兩個東西是非常重要的,第一個是void,也就是這個委托可以注冊(代表)的函數的返回值,其次便是args,這個是該委托可以注冊(代表)的函數所使用的參數。換句話說,所有的以string為參數,以void為返回值(這里暫時這么說)的函數都是可以通過這個委托進行注冊的!
我想看到這個大家就應該很有感觸了,一個方法,除了方法名,最能標志它的東西無非就是返回值和參數,至于修飾符,后面再提,所以委托可以說是抓住了方法的核心成分,置于方法名么,這個自然好理解,如果把方法名也通過委托表現出來,那就不需要委托的存在了,其實后面還有一個用處,那就是多播委托~后面再講
二、委托的使用
委托的使用分為三個步驟:1.委托聲明 2.委托注冊 3.委托調用a. 委托聲明,舉個例子:public void delegate Print(string name);解說同上!
b. 委托注冊:現在假設有一個函數:
public void speakEnglish(string say){
Console.WriteLine(say);
}
注冊方法:Print english = new Print(speakEnglish);
解說:委托其實可以被看作是一個特殊的類,很明顯,這里聲明的就是該類的實例english,我們再注意一下這里的構造函數所使用的參數,它是一個方法名,該方法必須和委托聲明的方法簽名一致,否則會拋出異常;上面只是一種注冊方式,還有其他便捷的方法~
c. 委托調用:english("hello"); 這個時候控制臺便會輸出hello字符,委托調用成功!
#########################################華麗麗的分割線#############################################
以上所描述的是最最基本的委托使用過程,接下來引申開來,繼續深入
三、多播委托
多播委托對于委托實現其價值來說真的是很重要的,比如在事件監聽中的使用。
多播委托的特點:
1)多播委托,顧名思義,就是說可以通過委托一次調用一個以上的方法;
2)多播委托聲明時必須返回void,否則會拋出異常;
3)多播委托一旦遇到一個函數拋出異常,則會停止執行剩余的函數;(這個是可以解決的)
這里也可以體會到為什么不需要把方法名通過委托表現出來的原因。
下面舉一個多播委托的例子:延續上面的例子(注:多播委托和一般委托的聲明并沒有什么決定性區別,唯一要注意的就是上面說的特點2)
假設還有一個方法:
public void speakChinese(string say){
Console.WriteLine(say);
}
下面需要同時調用兩個方法,那么怎么通過多播委托實現呢?
Print print = new Print(speakEnlish);
print += speakChinese;
print("Hello, 中國!");
這樣的話控制臺上就會出現兩邊Hello,中國字符。
解釋:1)很明顯,多播委托不管注冊多少方法,其方法簽名一致的要求是不可以更改的!
2)注冊的時候首先聲名委托的實例,然后可以通過“+=”運算符添加(注冊)更多的方法!同理可知,也可以通過運算符“-=”取消注冊;
3)注冊完畢后,一旦如同一般委托通過委托實例傳入參數則所有注冊過的方法都會接收參數運行一遍!并且是沒有執行順序的!
通過3)可以解釋為什么返回值必須是void,甚至返回值是同一個類型都不可以!因為多播委托中注冊的方法是一起調用的,如果有返回值,比如一個返回1,一個返回2,那么就會出現一個函數(委托)返回多個值的情況,總會出錯!所以必須為Void.
到這里,我想具體什么是委托,怎么使用委托基本上比較清楚了,下面我最最關心的就是到底委托會被用在哪些地方呢?委托是C#中比較獨特的一個技術,存在即是真理,它的優點在什么地方?(很不幸,我C++沒學好,關于C++函數指針的有點也沒參透,所以不能遷移過來)
####################################
委托的能力######################################
大家在使用集合時,一定使用過Sort類似的方法,這個方法就是用來對集合中的元素進行排序的,對于一般元素,比如int等等,它內部就嵌有比較方法在里面,然而,很多時候我們需要比較的往往不是這么簡單的東西,很多時候我們要比較的是我們自己定義的類型。而Sort方法中也有一個構造函數,是需要我們自己傳入比較函數的,這里就是一個使用委托的絕佳范例。下面引用的例子來自《C#高級編程(第6版)》(有興趣的話可以去看看,書不錯,就是厚了點)
程序1:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Wrox.ProCSharp.Delegates
6 {
7 class Employee
8 {
9 private string name;
10 private decimal salary;
11
12 public Employee(string name, decimal salary)
13 {
14 this.name = name;
15 this.salary = salary;
16 }
17
18 public override string ToString()
19 {
20 return string.Format("{0}, {1:C}", name, salary);
21 }
22
23 public static bool CompareSalary(object x, object y)
24 {
25 Employee e1 = (Employee)x;
26 Employee e2 = (Employee)y;
27 return (e1.salary < e2.salary);
28 }
29 }
30 }
這個Employee類里面定義了一個比較方法,CompareSalary,這個方法就是等會兒需要注冊委托的方法
程序2:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Wrox.ProCSharp.Delegates
6 {
7 delegate bool Comparison(object x, object y);
8
9 class BubbleSorter
10 {
11 static public void Sort(object[] sortArray, Comparison comparer)
12 {
13 for (int i = 0; i < sortArray.Length; i++)
14 {
15 for (int j = i + 1; j < sortArray.Length; j++)
16 {
17 if (comparer(sortArray[j], sortArray[i]))
18 {
19 object temp = sortArray[i];
20 sortArray[i] = sortArray[j];
21 sortArray[j] = temp;
22 }
23 }
24 }
25 }
26 }
27 }
這個類里面聲明了一個委托,可以看到這個委托和上面Employee類里面的方法簽名是一樣的,也就是說可以通過這個代理注冊上面的CompareSalary方法
程序3:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Wrox.ProCSharp.Delegates
6 {
7
8 class Program
9 {
10 static void Main()
11 {
12 Employee[] employees =
13 {
14 new Employee("Bugs Bunny", 20000),
15 new Employee("Elmer Fudd", 10000),
16 new Employee("Daffy Duck", 25000),
17 new Employee("Wiley Coyote", (decimal)1000000.38),
18 new Employee("Foghorn Leghorn", 23000),
19 new Employee("RoadRunner'", 50000)};
20
21 BubbleSorter.Sort(employees, Employee.CompareSalary);
22
23 foreach (var employee in employees)
24 {
25 Console.WriteLine(employee);
26 }
27 //for (int i = 0; i < employees.Length; i++)
28 // Console.WriteLine(employees[i].ToString());
29 }
30 }
31 }
我們看一下這里是怎么使用委托的!
首先聲明了一個Employee數組,目標是對里面的元素盡心排序,我們看到,排序使用的方法是靜態的Sort()方法,傳入了一個數組和一個方法名,而在Sort()方法的參數列表里面大家可以看到,與方法名對應的位置是委托的實例,也就是說,我們把比較的方法通過委托傳給了比較函數,這樣,我們就能夠通過改變比較的方法來實現比較我們自己定義的類型的目的!
個人感覺這樣的實現很方便,尤其是將比較函數分離出來,這樣符合程序的“開閉原則”,將需要變化,可能變化的部分剝離了出來~~當然,委托的能力不是這樣一個例子可以說清楚的,況且這里還沒有列出多播委托的例子,在窗體事件中每個多播委托可以實現一個事件激發多個監聽者的功能,所以說到底委托有什么好處,該怎么使用,都需要多多實踐才能更多的領悟~
接下來還有些東西值得侃一侃:
1.委托之匿名方法:委托不但可以為已經存在的方法注冊,而且在編輯過程中可以直接添加一個匿名方法,換句話說,就是臨時寫一個方法放入注冊列表中,下面是一個示例,延續上面的案例:
Print print = delegate(string say){
Console.WriteLine(say);
}這里注冊就不是一個方法了,而是一個代碼塊,關鍵字是delegate,這是不能更改的,后面的傳入參數以及里面方法的返回值(如果有返回值的情況)必須要和委托聲明的一致!
這里我有一個疑問,這種匿名方法的委托使用是不是可以用在多播委托中?按常理應該是可以添加的,那么怎么取消注冊呢?(沒有方法名的情況下是不可能使用-=的嘛~)2.委托:
λ表達式是C#3.0新提供的語法,用于委托類型以簡化代碼,事實上,就是委托形式的一種符號表達形式,下面是一個示例:
上面的匿名方法的例子是可以這么用表達式寫的:
Print print => (say) {
Console.WriteLine(say);
}
上面的表達式很奇怪吧?say這個變量沒有實現聲明就開始使用了對吧?解釋如下:
其實say是聲明過的,在哪里聲明的呢?對,沒錯,就是第一次使用的時候聲明的,但是很明顯,這里根本不像變量聲明??!根本就沒有指出類型嘛,這就是委托的獨特之處,在聲明委托的時候就已經指出了參數的類型,在這里表達式是專門代替委托而存在的,也就相當于在這里指出了say的類型!
下面再看一個例子就知道表達式和委托的關系了:
委托聲明:public delegate bool Predicate(int object);
看下面的使用方法:
Predicate p1 = x => x>5;
list.findAll();
第二種使用方式相當于:list.findAll(
delegate(int x){return x>5}
)
;
對比上面兩條語句,是否有所感悟?
3.委托之協變與抗變
這個我想也不太常遇到,其實就是委托的參數與返回值和父類與子類之間的一種轉換,這里就不羅哩羅嗦了~~
寫寫寫寫發現有點像是整理,不過想說的也基本上說在這里了,歡迎留下意見,也歡迎留下問題互相交流~