【C#系列】你應該知道的委託和事件

本篇文章更適合具備必定開發經驗,必定功底,且對底層代碼有所研究的朋友!!!編程

 

本篇文章主要採用理論和代碼實例相結合方式來論述委託和事件,涉及到一些邊界技術,如軟件架構的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 }
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。
  • 能夠轉載該博客,但必須著名博客來源。
相關文章
相關標籤/搜索