關於委托和事件的使用

来源:https://www.cnblogs.com/garyshao/archive/2018/06/01/9121183.html
-Advertisement-
Play Games

原文:https://www.codeproject.com/articles/85296/important-uses-of-delegates-and-events 原文作者: Shivprasad koirala 介紹 在這篇文章中, 我們會嘗試著去理解delegate能解決什麼樣的問題, 然 ...


原文:https://www.codeproject.com/articles/85296/important-uses-of-delegates-and-events 原文作者: Shivprasad koirala   

介紹

在這篇文章中, 我們會嘗試著去理解delegate能解決什麼樣的問題, 然後會在實例中去使用。 之後, 我們要進一步理解多播委托的概念以及事件是如何封裝委托的。 最終, 我們要明白事件和委托的不同, 學會如何非同步調用委托。   在文章的最後,我們能能總結出委托的六種重要用處。  

方法和函數的抽象問題

在講委托之前,讓我們先搞明白委托到底能解決什麼問題。下麵是一個很簡單的類“ClsMaths”, 它只有一個方法“Add”。這個類會被一個簡單的客戶端消費(調用)。假設過了一段時間之後,現在客戶端對ClsMaths這個類有了新的需求: 添加一個"Subtration"方法。那麼,按之前的做法, 我們需要修改客戶端已添加對新方法的調用代碼。 換句話說, ClsMaths的一個新增方法導致了客戶端的重新編譯。     簡單來說, 問題出現了: 功能類和消費類之間存在了緊耦合。所以如何解決?  我們可以選擇使用委托作為中間件(墊片), 消費類不再是直接調用實現類的方法,而是調用一個虛擬指針(委托),讓委托去調用真正的執行方法。這樣,我們就把消費類和具體實現方法解耦了。 譯者註, 他這裡的ClsMaths類只有四個方法 加減乘除, 作者使用了一個委托變數來調用4個方法, 所以這裡確實做到瞭解耦。   稍後你就可以看到因為抽象指針的作用,ClsMath的修改將不會對消費類產生任何影響。 這裡的抽象指針就是委托啦。 /** 題外話,上圖提到的Balsamiq Mockups是一個很棒的軟體, 可以用來畫UI效果圖, 我喜歡用來畫流程圖(稍顯不如visio方便, 但是閱讀和美觀效果完爆之) **/  

如何創建一個委托

創建一個委托只要四步: 定義, 創建, 引用, 調用(和C# in depth 中的說法一致) 第一步是定義一個和函數有同樣返回類型、輸入參數的委托, 例如下麵的Add函數有2個int類型輸入參數以及一個int類型的輸入參數。
1 private int Add(int i,int y)
2 {
3     return i + y;
4 }

 

對此, 我們可以定義如下的委托:
1 // Declare delegate
2 public delegate int PointetoAddFunction(int i,int y);

 

註意, 返回類型和輸入類型要相容, 否則會報錯。   下一步就是創建一個委托類型的變數嘍:
1 // Create delegate reference
2 PointetoAddFunction myptr = null;

 

最後就是調用了:
1 // Invoke the delegate
2 myptr.Invoke(20, 10)

 

下圖為實例代碼:  

如何使用委托解決抽象指針問題

為瞭解耦演算法的變化, 我們使用一個抽象的指針指向所有的演算法:(因為這四個方法的格式是一致的)     第一步, 在實現類中定義一個委托如下:(註意輸入輸出參數的格式)
1 public class clsMaths
2 {
3   public delegate int PointerMaths(int i, int y);
4 }

 

第二步, 定義一個返回委托的函數用以暴露具體實現方法給消費類:
 1 public class clsMaths
 2 {
 3   public delegate int PointerMaths(int i, int y);
 4 
 5   public PointerMaths getPointer(int intoperation)
 6   {
 7     PointerMaths objpointer = null;
 8     if (intoperation == 1)
 9     {
10       objpointer = Add;
11     }
12     else if (intoperation == 2)
13     {
14       objpointer = Sub;
15     }
16     else if (intoperation == 3)
17     {
18       objpointer = Multi;
19     }
20     else if (intoperation == 4)
21     {
22       objpointer = Div;
23     }
24     return objpointer;
25   }
26 }

 

下麵就是完整的代碼, 所有的具體實現函數都被標記為private, 只有委托和暴露委托的函數是public的。
 1 public class clsMaths
 2 {
 3   public delegate int PointerMaths(int i, int y);
 4 
 5   public PointerMaths getPointer(int intoperation)
 6   {
 7     PointerMaths objpointer = null;
 8     if (intoperation == 1)
 9     {
10       objpointer = Add;
11     }
12     else if (intoperation == 2)
13     {
14       objpointer = Sub;
15     }
16     else if (intoperation == 3)
17     {
18       objpointer = Multi;
19     }
20     else if (intoperation == 4)
21     {
22       objpointer = Div;
23     }
24     return objpointer;
25   }
26 
27   private int Add(int i, int y)
28   {
29     return i + y;
30   }
31   private int Sub(int i, int y)
32   {
33     return i - y;
34   }
35   private int Multi(int i, int y)
36   {
37     return i * y;
38   }
39   private int Div(int i, int y)
40   {
41     return i / y;
42   }
43 }

 

所以消費類的調用就和具體實現方法沒有耦合了:
1 int intResult = objMath.getPointer(intOPeration).Invoke(intNumber1,intNumber2);

 

 

多播委托

在我們之前的例子中,我們已經知道瞭如何創建委托變數和綁定具體實現方法到變數上。但實際上, 我們可以給一個委托附上若幹個具體實現方法。如果我們調用這樣的委托, 那麼附到委托上的函數會順序執行。(至於如果函數有返回值, 那麼只有最後一個函數的返回值會被捕捉到)
1 // Associate method1
2 delegateptr += Method1;
3 // Associate Method2
4 delegateptr += Method2;
5 // Invoke the Method1 and Method2 sequentially
6 delegateptr.Invoke();

 

所以, 我們可以在“發佈者/消費者”模式中使用多播委托。例如, 我們的應用中需要不同類型的錯誤日誌處理方式,當錯誤發生時,我們需要把錯誤信息廣播給不同的組件進行不同的處理。 (如下圖)  

多播委托的簡單例子

我們可以通過下麵這個例子更好的理解多播委托。 在這個窗體項目中,我們有“Form1”, “Form2”, “Form3”。 “Form1中有一個多播委托來把動作的影響傳遞到“Form2”和“Form3”中。   在"Form1"中, 我們首先定義一個委托以及委托變數, 這個委托是用來傳遞動作的影響到其他Form中的。
1 // Create a simple delegate
2 public delegate void CallEveryOne();
3 
4 // Create a reference to the delegate
5 public CallEveryOne ptrcall=null;
6 // Create objects of both forms
7 
8 public Form2 obj= new Form2();
9 public Form3 obj1= new Form3();

 

在“Form1”的Form_Load函數中, 我們調用其他的Forms;把其他表單中的CallMe方法附加到“Form1”的委托中。
1 private void Form1_Load(object sender, EventArgs e)
2 {
3   // Show both the forms
4   obj.Show();
5   obj1.Show();
6   // Attach the form methods where you will make call back
7   ptrcall += obj.CallMe;
8   ptrcall += obj1.CallMe;
9 }

 

最終, 我們在"Form1"的按鈕點擊函數中調用委托(多播的):
1 private void button1_Click(object sender, EventArgs e)
2 {
3   // Invoke the delegate
4   ptrcall.Invoke();
5 }

 

 

多播委托的問題 -- 暴露過多的信息

  上面例子的第一個問題就是, 消費者並沒有權利來選擇訂閱或是不訂閱,因為這個過程是由“Form1”也就是發佈者來決定的。  我們可以用其他方式, 把委托傳遞給消費者, 讓消費者來決定他們要不要訂閱來自發佈者(Form1)的多播委托。 但是, 這種做法會引發另個問題: 破壞封裝。 如果我們把委托暴露給消費者, 就意味著委托完全裸露在了消費者面前。   

事件 -- 委托的封裝

事件能解決委托的封裝問題。 事件包裹在委托之外, 使得消費者只能接收但不會有委托的完全控制權。 下圖是對這一概念的圖解: 1. 具體的實現方法被委托抽象和封裝了 2. 委托被多播委托進一步封裝了以提供廣播的效果 3. 事件進一步封裝了多播委托  

實現事件

我們來把多播委托的例子改造成事件的方式。 第一步是在發佈者“Form1”中定義委托和委托類型的事件; 下麵就是對應的代碼塊,請註意關鍵字event。 我們定義了一個委托“CallEveryone”, 然後定義了一個委托類型的事件“EventCallEveryone”。
1 public delegate void CallEveryone();
2 public event CallEveryone EventCallEveryOne;

 

從發佈者“Form1”中創建“Form2”和“Form3”的對象, 然後把當前這個“Form1”對象傳到“Form2”、 "Form3"中, 這樣 2、 3就可以監聽事件了。  
1 Form2 obj = F new Form2();
2 obj.obj = this;
3 Form3 obj1 = new Form3();
4 obj1.obj = this;
5 obj.Show();
6 obj1.Show();
7 EventCallEveryOne();

 

在消費者這邊, “Form2”和“Form3”自主決定是否把具體某個方法付到事件上。
1 obj.EventCallEveryOne += Callme;

 

這段代碼的執行結果將會和我們上文的多播委托的例子結果一樣。   

委托和事件的不同

  所以, 如果事件不是委托的語法糖那麼他們之間的區別在哪?  我們在上文中已經提到了一個主要的區別: 事件比委托多了一層封裝。因此, 如果我們傳遞委托, 那麼消費者接受的是一個赤裸裸的委托, 用戶可以修改委托的信息。 而我們使用事件,那麼用戶只能監聽事件而不能修改它。  函數的非同步委托調用 委托的另一種用法是非同步函數的調用。 你能夠非同步的調用委托指向的函數。    非同步調用意味著客戶端調用委托之後, 代碼的控制權又立即回到了客戶端手中以繼續執行後續的代碼。  委托攜帶者調用者的信息在parallel的線程池中啟用新的線程執行具體的函數, 當委托執行結束後, 它會發出信息通知客戶端(調用者)。  為了能夠非同步得調用函數, 我們需要call “begininvoke”方法。 在“begininvoke”方法中, 我們需要為委托提供一個回調函數。 如下圖的CallbackMethod。 
1 delegateptr.BeginInvoke(new AsyncCallback(CallbackMethod), delegateptr);

 

下麵的代碼段就是一個回調函數的demo, 這段代碼會在委托中的函數執行完成之後被立即調用。
1 static void CallbackMethod(IAsyncResult result)
2 {
3   int returnValue = flusher.EndInvoke(result);
4 }

 

總結委托的用法

委托有5種重要的使用方式:(譯者註: 原文寫的6種, 我只看到了5種) 1. 抽象、封裝一個方法(匿名調用)     這是委托最重要的功能, 它幫助我們定義一個能夠指向函數的抽象的指針。 同時, 這個指針還可以指向其他符合其規範的函數。 
開頭的時候我們展示的一個math類, 之後我們使用一個抽象指針就把該math類中添加的所有函數都包括在內了。 這個例子就很好的說明瞭委托的這一使用方法。  2. 回調機制     客戶端可以使用委托來穿件回調函數。 
3. 非同步執行     使用BeginInvoke 和 EndInvoke 我們可以非同步得調用所有的委托。 
4. 多點廣播     有的時候, 我們希望能夠讓函數順序執行, 那麼多播委托就可以做到這一點。 
5. 事件     事件可以幫助我們方便的建立 發佈/訂閱 模式。 
     
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JSP總結 靜態網頁 在網站設計中,純粹HTML(標準通用標記語言下的一個應用)格式的網頁通常被稱為“靜態網頁”,靜態網頁是標準的HTML文件,它的文件擴展名是.htm、.html 。靜態網頁是網站建設的基礎,早期的網站一般都是由靜態網頁製作的。靜態網頁是相對於動態網頁而言,是指沒有後臺資料庫、不含 ...
  • Eclipse環境下如何配置Tomcat,並且把項目部署到Tomcat伺服器上 ...
  • 1.新建web項目,添加兩個Button控制項,結果如圖。 2.Button按鈕控制項點擊事件代碼如下 點擊Button1控制項: 可以看到動態生成的文本框的值成功獲取到。 但是點擊Button2會出現如下結果: 原因是因為動態生成的文本框其實是HTML控制項,所以獲取文本框的值 控時,要註意獲取的方法,不 ...
  • 基於ASP.NET 4.0開發的開源微商城系統,我們的目標是構建一個核心完善而又輕量級的微商城平臺,目前基本的核心功能,包括微信登陸/支付,產品管理,購物車與訂單管理等,輕量級是為了更加便於理解源碼和二次開發。 使用技術 ASP.NET 4.0 MySql Server 環境要求 支持ASP.NET ...
  • 一、如要使用SQLite,可以從Visual Studio中的“程式包管理器控制台”輸入以下命令完成安裝: SQLite則會安裝到項目中,支持32位或64位,如下圖所示: 二、新建一個SQLite資料庫,名稱命名為Test.db,其表名稱及列定義如下: 三、新建一個控制台應用的解決方案,並輸入以下代 ...
  • 今天看到 叫我藍火火 s的 UWP中實現大爆炸效果(一) ,我也來說一下我的app 【小薇自然語言處理】實現的大爆炸技術。 看一下效果先。 我的控制項是基於wrappanel的,正如藍火火說的,這樣看來是很整齊,他不喜歡這樣的。不過我倒是覺得還行。哈哈😂 程式員也是眾口難調,哈 大爆炸技術主要分為兩 ...
  • 一直不清楚服務端是如何判斷一個請求是否是ajax請求,通過ILSpy查看,才得知是通過判斷請求頭是否存在 X-Requested-With:XMLHttpRequest 來判斷是否是ajax請求。 ...
  • 讀【C#併發編程經典實例.PDF】一書總結: 1、併發:同時做多件事。 2、多線程:併發的一種形式,它採用多個線程來執行程式。所以多線程只是實現併發的一種方法,併發不等於多線程。 3、並行處理:把正在執行的大量任務分隔成小塊,分配給多個正在運行的線程。 並行處理是多線程的一種,多線程是併發的一種。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...