C# 委托和事件高級進階


本篇文章主要采用理論和代碼實例相結合方式來論述委托和事件,涉及到一些邊界技術,如軟件架構的OCP原則(開-閉原則),

軟件架構解耦,設計模式(Sender-Order)和事件驅動模型,有一定難度和深度,不適合初級者。

第一部份   委托

關於委托內容,主要圍繞下圖來論述。

 一   委托是什么(what)

(一)委托產生的背景之一

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 }
View Code

 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 }
View Code

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 }
View Code

 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 }
View Code

另一種不太規范寫法:不用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 }
View Code

之所以不規范,主要是在項目中,不利於代碼的模塊化。

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 
View Code

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 }
View Code

(三)委托機制

 將如下代碼通過反匯編工具.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 
View Code

反匯編

 分析:

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 
View Code

四 事件機制

 事件的本質就是委托,向外提供兩個訪問方法add_EventName(對應+=)和remove-EventName(對應-=),我們通過.NET Reflector反匯編工具來查看,到底是不是這樣的。

 

參考文獻

【01】C#高級編程(第七版)  (Christian Nagel,Bill Evjen和Jay Glynn 編著,李銘 譯,黃靜 審校) 

版權區

  • 感謝您的閱讀,若有不足之處,歡迎指教,共同學習、共同進步。
  • 博主網址:http://www.cnblogs.com/wangjiming/。
  • 極少部分文章利用讀書、參考、引用、抄襲、復制和粘貼等多種方式整合而成的,大部分為原創。
  • 如您喜歡,麻煩推薦一下;如您有新想法,歡迎提出,郵箱:2098469527@qq.com。
  • 可以轉載該博客,但必須著名博客來源。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM