本篇文章更適合具備必定開發經驗,必定功底,且對底層代碼有所研究的朋友!!!編程
本篇文章主要採用理論和代碼實例相結合方式來論述委託和事件,涉及到一些邊界技術,如軟件架構的OCP原則(開-閉原則),設計模式
軟件架構解耦,設計模式(Sender-Order)和事件驅動模型,有必定難度和深度,不適合初級者。安全
第一部份 委託架構
關於委託內容,主要圍繞下圖來論述。異步
一 委託是什麼(what)ide
(一)委託產生的背景之一模塊化
1.咱們先來假設這樣一個情景需求:函數
設計一個系統,使其知足以下條件:工具
(1)當前,只有中國人和英國人使用該系統;學習
(2)向系統輸入用戶名和相應的語言,將產生相應語言的問候語;
(3)後期,可能會有其餘國家語言加入該系統(系統變化的部分) ;
2.技術方案實現
關於技術方案實現,咱們能夠採用下圖中的三種方式之一。
爲了更好地敘述委託,咱們分別實現三種技術方案,並找出它們的關係。
2.1 通常實現
Code(控制檯程序)
1 using System; 2 3 namespace DelegateDemo 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Console.WriteLine(GetGreetingContens("小王", "Chinese")); 10 Console.WriteLine(GetGreetingContens("Alan_beijing", "English")); 11 Console.WriteLine(GetGreetingContens("Linda", "Russian")); 12 Console.Read(); 13 } 14 15 //根據用戶名和語言,獲取問候語 16 public static string GetGreetingContens(string UserName, string Language) 17 { 18 //New 一個GreetToUsers對象 19 GreetToUsers greetToUsers = new GreetToUsers(); 20 //固然,你也可使用switch開發語句來代替以下的if......else...... 21 if (Language == "Chinese") 22 { 23 return greetToUsers.ChinesePeople(UserName); 24 } 25 else if (Language == "English") 26 { 27 return greetToUsers.EnglishPeople(UserName); 28 } 29 else 30 { 31 return "抱歉,當前系統只支持漢語與英語(Sorry, the current system only supports Chinese and English.)"; 32 } 33 } 34 } 35 36 37 38 //定義基本問候類和方法 39 public class GreetToUsers 40 { 41 //Chinese People 42 public string ChinesePeople(string UserName) 43 { 44 string GreetContents = "您好!" + UserName; 45 return GreetContents; 46 } 47 48 //English People 49 public string EnglishPeople(string UserName) 50 { 51 string GreetContents = "Hello," + UserName + "!"; 52 return GreetContents; 53 } 54 } 55 56 }
Result
分析
2.2用接口實現
如上,咱們分析了方案一中的問題,爲了更好地解決方案一存在的問題,咱們採用面向接口編程的形式來實現。
2.2.1 什麼是面向接口編程?
面向接口編程,主要是解決軟件架構設計中「動靜問題」,即封裝不變(靜),剝離變化(抽出變化)。
Code:
1 using System; 2 3 namespace DelegateDemo 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 GreetToChineseUsers greetToChinesUsers = new GreetToChineseUsers(); 10 GreetToEnglishUsers greetToEnglishUsers = new GreetToEnglishUsers(); 11 GreetToOtherUsers greetToOtherUsers = new GreetToOtherUsers(); 12 //Chinse Users 13 IGreetToUsers iGreetToChineseUsers = greetToChinesUsers; 14 Console.WriteLine(iGreetToChineseUsers.CountryPeople("小王", "Chinese")); 15 //English Users 16 IGreetToUsers iGreetToEnglishUsers = greetToEnglishUsers; 17 Console.WriteLine(iGreetToEnglishUsers.CountryPeople("Alan_beijing", "English")); 18 //Other Users 19 IGreetToUsers iGreetToOtherUsers = greetToOtherUsers; 20 Console.WriteLine(iGreetToOtherUsers.CountryPeople("Linda", "Russian")); 21 22 Console.Read(); 23 } 24 25 26 } 27 28 //系統輸出問候語(變化的部分,語言爲變化因子) 29 public interface IGreetToUsers 30 { 31 string CountryPeople(string UserName,string Language); 32 } 33 34 35 //漢語用戶類 36 public class GreetToChineseUsers:IGreetToUsers 37 { 38 //Chinese People 39 public string CountryPeople(string UserName, string Language) 40 { 41 string GreetContents = "您好!" + UserName; 42 return GreetContents; 43 } 44 } 45 46 //英語用戶類 47 public class GreetToEnglishUsers : IGreetToUsers 48 { 49 //English People 50 public string CountryPeople(string UserName, string Language) 51 { 52 string GreetContents = "Hello," + UserName + "!"; 53 return GreetContents; 54 } 55 } 56 57 //其餘用戶類 58 public class GreetToOtherUsers : IGreetToUsers 59 { 60 //English People 61 public string CountryPeople(string UserName, string Language) 62 { 63 return "Sorrry,當前系統只支持漢語與英語"; 64 } 65 } 66 67 }
result
分析:
(1)如上,咱們將變化因子"語言"剝離出來,造成接口,之後只要每增長一個語言,只需實現接口便可,知足了OCP原則,基本解決了方案一中存在的問題;
(2)如上代碼只是爲了演示面向接口編程這個功能,並不完善,感興趣的讀者,可自行完善(如將語言定義爲枚舉類型等);
方案二中的代碼,細心的讀者會發現,Main方法中new了三個對象,倘若之後系統有300門語言,那豈不New 300個類,這樣的話,也不利於代碼維護呀,怎麼解決這個問題呢?(提示一下,採用設計模式抽象工廠便可解決該問題)
2.3 用委託實現
在這裏,沒接觸過委託的讀者,先跳過這部分,往下讀,看完(三)怎樣使用委託(How to use)後,再來看本部分。
Code
1 using System; 2 3 namespace DelegateDemo 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 //根據語言判斷,傳遞哪一個方法的參數 10 Console.WriteLine("------------請輸入用戶名------------"); 11 string UserName = Console.ReadLine(); 12 Console.WriteLine("------------請輸入語言------------"); 13 string Language = Console.ReadLine(); 14 Console.WriteLine("------------輸出結果------------"); 15 GreetToUsers greetToUsers = new GreetToUsers(); 16 if (Language == "Chinese") 17 { 18 Console.WriteLine(GetGreetingContents(UserName, greetToUsers.ChinesePeople)); 19 } 20 else if (Language == "English") 21 { 22 Console.WriteLine(GetGreetingContents(UserName, greetToUsers.EnglishPeople)); 23 } 24 else 25 { 26 Console.WriteLine(GetGreetingContents(UserName, greetToUsers.OtherPeople)); 27 } 28 Console.Read(); 29 } 30 31 32 33 public static string GetGreetingContents(string UserName,DelegateGetGreeting delegateGetGreeting) 34 { 35 return delegateGetGreeting(UserName); 36 } 37 } 38 39 //定義委託 40 public delegate string DelegateGetGreeting(string UserName); 41 42 43 //定義基本問候類和方法 44 public class GreetToUsers 45 { 46 //Chinese People 47 public string ChinesePeople(string UserName) 48 { 49 string GreetContents = "您好!" + UserName; 50 return GreetContents; 51 } 52 53 //English People 54 public string EnglishPeople(string UserName) 55 { 56 string GreetContents = "Hello," + UserName + "!"; 57 return GreetContents; 58 } 59 //非英非漢 60 public string OtherPeople(string UserName) 61 { 62 return "Sorrry,當前系統只支持漢語與英語"; 63 } 64 } 65 66 }
Result
2.3 分析上述三種實現方案的關係
經過上訴三種方式的比較,咱們容易得出委託的以下結論:
(1)抽象方法,屏蔽方法細節,調用只需傳遞方法名字便可;
(2)可以實現程序的解耦,鬆耦合(在方案一中,GetGreetingContens方法體內new了GreetToUsers對象,強耦合)
(3)委託通常扮演中間者的角色,這功能在委託事件中體現很是明顯(第二部分 事件 將詳細論述)
如咱們在租房子時,能夠直接找房東(技術實現的通常方法,強耦合,讓租房者和房東直接聯繫),也可找中介(技術實現的委託,鬆耦合,租房者經過中介來與房東聯繫)
2.4 委託背景概述
委託的重要產生背景,就是事件驅動模型(關於什麼是事件和事件驅動,在本文第二部份 事件 論述)。
(二) 委託定義
用delegate關鍵字定義委託(注意,委託是沒有方法體的,相似接口裏面的方法),在定義委託前,必須明確兩個問題:
1.委託將要綁定的方法;
2.委託的形參類型,形參個數和委託的返回值必須與將要綁定的方法的形參類型,形參個數和返回值一致;
(三)相關概念
委託涉及的相關概念有函數指針,類型安全性、事件、Lambda表達式等
1.函數指針:在C++中,指針的一個類別,主要指向函數(變量指針,主要指向變量地址),能夠把C#中的委託理解爲函數指針;
2.類型安全性:在C++中,咱們都知道指針是類型不安全的(返回值,返回類型和何時返回,這些都是未知的),而委託是類型安全的;
3.事件:能夠把事件理解爲委託的一種特例(在本文第二部份 事件 論述)
4.Lambda表達式:委託與Lambd表達式相結合,實現高效編程,與Jquery的「較少代碼作更多的事」相似,委託與Lambda,Linq相結合,使較短代碼就能實現比較複雜的功能(在本篇文章中不講解Lambda與Lambda樹,將在後續文章中講解)
(四)委託組成
大體分爲兩部分:聲明委託和註冊方法(也叫綁定方法)
1.聲明委託
用delegate聲明;
2.綁定方法
綁定具體方法,傳遞方法名稱;
(五) 委託種類
委託種類,通常分爲多播委託和單播委託
1.單播委託:綁定單個方法
2.綁定多個方法
(六) 委託操做
1.綁定方法
2.解綁方法
二 委託能解決什麼問題(Can do)
1.避免核心方法中存在大量的if....else....語句(或swich開關語句);
2.知足程序設計的OCP原則;
3.使程序具備擴展性;
4.綁定事件;
5.結合Lambda表達式,簡化代碼,高效編程;
6.實現程序的鬆耦合(解耦),這個在事件(event)中體現比較明顯;
三 怎麼使用委託(How to use)(本篇文章不談匿名委託,匿名委託具體內容,將在Lambda章節講解)
(一)委託的基本構成
一般地,使用委託的步驟與使用類的步驟是同樣的。大體分爲兩步:定義委託和綁定方法(傳遞方法)
1.定義委託
用delegate關鍵字定義委託(注意,委託是沒有方法體的,相似接口裏面的方法),在定義委託前,必須明確兩個問題:
(1).委託將要綁定的方法;
(2).委託的形參類型,形參個數和委託的返回值必須與將要綁定的方法的形參類型,形參個數和返回值一致;
public delegate 委託返回類型 委託名(形參)
例子:如上咱們委託將要表示系統輸出的問候語
a.委託將要綁定的方法
public string ChinesePeople(string UserName) { string GreetContents = "您好!" + UserName; return GreetContents; } //English People public string EnglishPeople(string UserName) { string GreetContents = "Hello," + UserName + "!"; return GreetContents; } //非英非漢 public string OtherPeople(string UserName) { return "Sorrry,當前系統只支持漢語與英語"; }
b.由如上方法可看出,方法的返回類型爲string,方法有一個string類型的形參,在定義委託時,與其保持一致便可
//定義委託 public delegate string DelegateGetGreeting(string UserName);
2.綁定方法
使用委託時,將方法名字做爲參數傳遞給委託便可
1 GreetToUsers greetToUsers = new GreetToUsers(); 2 GetGreetingContents(UserName, greetToUsers.ChinesePeople)
(二)委託綁定方法
1.綁定單個方法
綁定單個方法,將單個方法名字傳給委託便可
1 static void Main(string[] args) 2 { 3 //根據語言判斷,傳遞哪一個方法的參數 4 Console.WriteLine("------------請輸入用戶名------------"); 5 string UserName = Console.ReadLine(); 6 Console.WriteLine("------------請輸入語言------------"); 7 string Language = Console.ReadLine(); 8 Console.WriteLine("------------輸出結果------------"); 9 GreetToUsers greetToUsers = new GreetToUsers(); 10 DelegateGetGreeting DGG; 11 if (Language == "Chinese") 12 { 13 //綁定單個方法 14 DGG = greetToUsers.ChinesePeople; 15 Console.WriteLine(GetGreetingContents(UserName, DGG)); 16 } 17 else if (Language == "English") 18 { 19 DGG = greetToUsers.EnglishPeople; 20 Console.WriteLine(GetGreetingContents(UserName, DGG)); 21 } 22 else 23 { 24 DGG = greetToUsers.OtherPeople; 25 Console.WriteLine(GetGreetingContents(UserName, DGG)); 26 } 27 28 Console.Read(); 29 }
另外一種不太規範寫法:不用GetGreetingContents(string UserName,DelegateGetGreeting delegateGetGreeting)方法
1 static void Main(string[] args) 2 { 3 //根據語言判斷,傳遞哪一個方法的參數 4 Console.WriteLine("------------請輸入用戶名------------"); 5 string UserName = Console.ReadLine(); 6 Console.WriteLine("------------請輸入語言------------"); 7 string Language = Console.ReadLine(); 8 Console.WriteLine("------------輸出結果------------"); 9 GreetToUsers greetToUsers = new GreetToUsers(); 10 DelegateGetGreeting DGG; 11 if (Language == "Chinese") 12 { 13 //綁定單個方法 14 DGG = greetToUsers.ChinesePeople; 15 Console.WriteLine(DGG(UserName)); 16 } 17 else if (Language == "English") 18 { 19 DGG = greetToUsers.EnglishPeople; 20 Console.WriteLine(DGG(UserName)); 21 } 22 else 23 { 24 DGG = greetToUsers.OtherPeople; 25 Console.WriteLine(DGG(UserName)); 26 } 27 28 Console.Read(); 29 }
之因此不規範,主要是在項目中,不利於代碼的模塊化。
2.綁定多個方法(多播委託)
注意:綁定多個方法時,委託範圍類型必須爲void類型,不然只返回最後一個綁定的值。
綁定多個方法,採用 += 綁定
1 using System; 2 3 namespace DelegateDemo 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 10 GreetToUsers greetToUsers = new GreetToUsers(); 11 DelegateGetGreeting DGG; 12 13 //綁定多個方法 14 DGG = greetToUsers.ChinesePeople; 15 DGG += greetToUsers.EnglishPeople; 16 DGG("小王"); 17 18 Console.Read(); 19 } 20 21 22 } 23 24 //定義委託 25 public delegate void DelegateGetGreeting(string UserName); 26 27 28 //定義基本問候類和方法 29 public class GreetToUsers 30 { 31 //Chinese People 32 public void ChinesePeople(string UserName) 33 { 34 string GreetContents = "您好!" + UserName; 35 Console.WriteLine(GreetContents); 36 } 37 38 //English People 39 public void EnglishPeople(string UserName) 40 { 41 string GreetContents = "Hello," + UserName + "!"; 42 Console.WriteLine(GreetContents); 43 } 44 //非英非漢 45 public void OtherPeople(string UserName) 46 { 47 Console.WriteLine("Sorrry,當前系統只支持漢語與英語"); 48 } 49 } 50 51 } 52 53 54
3.解綁方法
解載綁定的方法,採用 -= 解綁
1 using System; 2 3 namespace DelegateDemo 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 10 GreetToUsers greetToUsers = new GreetToUsers(); 11 DelegateGetGreeting DGG; 12 13 //綁定多個方法 14 DGG = greetToUsers.ChinesePeople; 15 DGG += greetToUsers.EnglishPeople; 16 17 //解綁ChinesePeople方法 18 DGG-= greetToUsers.ChinesePeople; 19 DGG("小王"); 20 21 Console.Read(); 22 } 23 24 25 } 26 27 //定義委託 28 public delegate void DelegateGetGreeting(string UserName); 29 30 31 //定義基本問候類和方法 32 public class GreetToUsers 33 { 34 //Chinese People 35 public void ChinesePeople(string UserName) 36 { 37 string GreetContents = "您好!" + UserName; 38 Console.WriteLine(GreetContents); 39 } 40 41 //English People 42 public void EnglishPeople(string UserName) 43 { 44 string GreetContents = "Hello," + UserName + "!"; 45 Console.WriteLine(GreetContents); 46 } 47 //非英非漢 48 public void OtherPeople(string UserName) 49 { 50 Console.WriteLine("Sorrry,當前系統只支持漢語與英語"); 51 } 52 } 53 54 }
(三)委託機制
將以下代碼經過反彙編工具.NET Reflector反彙編
1 using System; 2 3 namespace DelegateDemo 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 10 GreetToUsers GTU = new GreetToUsers(); 11 12 DelegateGreet DG = new DelegateGreet(); 13 14 //DG.delegateGetGreeting = GTU.ChinesePeople;//註冊方法 15 DG.delegateGetGreeting += GTU.ChinesePeople; 16 DG.delegateGetGreeting += GTU.EnglishPeople; 17 DG.GreetUser("小王"); 18 19 Console.Read(); 20 } 21 } 22 23 public class DelegateGreet 24 { 25 //聲明委託 26 public delegate void DelegateGetGreeting(string UserName); 27 28 //委託變量爲public,破壞了類的封裝性 29 public DelegateGetGreeting delegateGetGreeting; 30 31 //雖然Event論定義爲public,但其仍是私有變量,只能經過+=,或-=訪問 32 //public event DelegateGetGreeting EventGreet; 33 34 public void GreetUser(string UserName) 35 { 36 //EventGreet?.Invoke(UserName); 37 delegateGetGreeting(UserName); 38 } 39 } 40 41 //定義基本問候類和方法 42 public class GreetToUsers 43 { 44 //Chinese People 45 public void ChinesePeople(string UserName) 46 { 47 string GreetContents = "您好!" + UserName; 48 Console.WriteLine(GreetContents); 49 } 50 51 //English People 52 public void EnglishPeople(string UserName) 53 { 54 string GreetContents = "Hello," + UserName + "!"; 55 Console.WriteLine(GreetContents); 56 } 57 //非英非漢 58 public void OtherPeople(string UserName) 59 { 60 Console.WriteLine("Sorrry,當前系統只支持漢語與英語"); 61 } 62 } 63 } 64 65
反彙編
分析:
1.三個核心方法:BeginInvoke,EndInvoke和Invoke
(1)使用Invoke完成一個委託方法的封送,就相似於使用SendMessage方法來給界面線程發送消息,是一個同步方法。也就是說在Invoke封送的方法被執行完畢前,Invoke方法不會返回,從而調用者線程將被阻塞。
(2使用BeginInvoke方法封送一個委託方法,相似於使用PostMessage進行通訊,這是一個異步方法。也就是該方法封送完畢後立刻返回,不會等待委託方法的執行結束,調用者線程將不會被阻塞。可是調用者也
可使用EndInvoke方法或者其它相似WaitHandle機制等待異步操做的完成。
總結:可是在內部實現上,Invoke和BeginInvoke都是用了PostMessage方法,從而避免了SendMessage帶來的問題。而Invoke方法的同步阻塞是靠WaitHandle機制來完成的。
提示:
最近瀏覽一篇文章,也講得不錯:http://blog.csdn.net/goodshot/article/details/6157529
要想深刻了解,請參照《CLR Via C#》,
第二部分 事件
關於事件(event),將會從以下四個角度來分析.
1.什麼是事件
2.事件能解決什麼問題
3.怎麼使用事件
4.事件機制
一 什麼是事件
談到委託,必提事件,事件本質是對委託的封裝,對外提供add_EventName(對應+=)和remove_EventName(對應-=)訪問,從而實現類的封裝性。
1.種類
強類型事件和弱類型事件
2.一些用處
(1)WebForm控件的Click事件。作過WebForm開發的朋友,可能對事件是很是熟悉的,如拖一個Button,雙擊,就自動在後臺生成Button的Click事件,以下圖所示。
原理:在Windows運用程序中,Button類提供了Click事件,其本質就是委託,當咱們觸發Click事件時,調用的處理程序方法須要參數,其參數就是由委託類型來定義的。
(2)設計模式發佈/訂閱。事件是基於委託的,爲委託提供了一種發佈/訂閱機制。
二 事件能解決哪些問題
1.將公有的委託變量定義爲私有變量,從而知足類的封裝性原則;
2.具備委託具備的做用;
三 如何使用事件
1.聲明委託
public delegate void DelegateGetGreeting(string UserName);
2.聲明事件
與委託聲明同樣,只不過多了一個關鍵字event
public event DelegateGetGreeting EventGreet;
3.時間註冊方法
事件註冊方法與委託註冊方法是同樣的。
1 DelegateGreet DG= new DelegateGreet(); 2 //DG.delegateGetGreeting = GTU.ChinesePeople;//註冊方法 3 DG.EventGreet+= GTU.ChinesePeople; 4 DG.EventGreet += GTU.EnglishPeople;
4.調用事件
調用定義事件的方法
DG.GreetUser("小王");
完整代碼以下:
1 using System; 2 3 namespace DelegateDemo 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 10 GreetToUsers GTU = new GreetToUsers(); 11 12 DelegateGreet DG= new DelegateGreet(); 13 14 //DG.delegateGetGreeting = GTU.ChinesePeople;//註冊方法 15 DG.EventGreet+= GTU.ChinesePeople; 16 DG.EventGreet += GTU.EnglishPeople; 17 DG.GreetUser("小王"); 18 19 Console.Read(); 20 21 } 22 23 24 } 25 public class DelegateGreet 26 { 27 //聲明委託 28 public delegate void DelegateGetGreeting(string UserName); 29 30 //委託變量爲public,破壞了類的封裝性 31 //public DelegateGetGreeting delegateGetGreeting; 32 33 //雖然Event論定義爲public,但其仍是私有變量,只能經過+=,或-=訪問 34 public event DelegateGetGreeting EventGreet; 35 36 public void GreetUser(string UserName) 37 { 38 EventGreet?.Invoke(UserName); 39 //delegateGetGreeting(UserName); 40 } 41 42 43 } 44 45 46 //定義基本問候類和方法 47 public class GreetToUsers 48 { 49 //Chinese People 50 public void ChinesePeople(string UserName) 51 { 52 string GreetContents = "您好!" + UserName; 53 Console.WriteLine(GreetContents); 54 } 55 56 //English People 57 public void EnglishPeople(string UserName) 58 { 59 string GreetContents = "Hello," + UserName + "!"; 60 Console.WriteLine(GreetContents); 61 } 62 //非英非漢 63 public void OtherPeople(string UserName) 64 { 65 Console.WriteLine("Sorrry,當前系統只支持漢語與英語"); 66 } 67 } 68 69 } 70 71 72
四 事件機制
事件的本質就是委託,向外提供兩個訪問方法add_EventName(對應+=)和remove-EventName(對應-=),咱們經過.NET Reflector反彙編工具來查看,究竟是不是這樣的。
參考文獻
【01】C#高級編程(第七版) (Christian Nagel,Bill Evjen和Jay Glynn 編著,李銘 譯,黃靜 審校)
版權區