.NET進階篇02-Delegate委托、Event事件


知識只有經過整理才能形成技能

內容目錄

一、概述二、解析委托知識點1、委托本質2、委托的使用3、委托意義邏輯解耦,減少重復代碼代碼封裝,支持擴展匿名方法和Lambda表達式異步多線程多播委托三、事件四、總結

一、概述

先說下委托,委托我們也經常用到。詳盡了解委托是必要的,不然在異步多線程的編程中會一頭霧水。委托本質就是一個類,和我們平常定義的類沒多大區別。只是這個類的作用的是描述一些方法,沒有數據成員。一個委托定義了一類擁有同樣返回類型和參數的方法規范。委托的聲明語法就是一個沒有方法體的方法前面加上delegate關鍵字。既然本質是一個類,那它就可以在任何可以定義普通類的位置來定義委托。委托是一個能把方法作為參數傳遞的對象

事件就簡單了,事件就是委托的一個實例

二、解析委托知識點

1、委托本質

在VS中編碼中,聲明委托后,會發現委托的着色提示和類時一樣。

但好像不是很有說服力。高級語法都做了很好的封裝,方便編碼人員。.NET的二次編譯,第一次編譯成IL中間語言,中間語言也是一種編程語言,只是它不像高級語言那么方便人類閱讀。我們可以通過一些工具(像ILSpy)反編譯來窺探下它的內部邏輯。

如圖中紅框所示,我們定義的普通類MyDelegate和委托類型NoReturnPara(繼承自MulticastDelegate)是一致的,都是class。在委托類型NoResultNoPara中也有.ctor(在IL中構造函數),此外還有我們以后會經常用到的Invoke方法和BeginInvoke、EndInvoke方法,前者是同步調用,后者是異步調用

2、委托的使用

我們使用委托一般就是三步走,第一步定義委托,第二部聲明委托實例,第三部調用。定義委托就像上面所示在一個沒有方法體的方法前加上delegate關鍵字即可。它給定了一種約束,只能用規定的方法結構(返回值和參數)的實例化委托。

public delegate void NoReturnNoPara();//1 聲明委托
NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);//2 委托的實例化
method.Invoke();//3 委托實例的調用
private void DoNothing()
{
        Console.WriteLine("This is DoNothing");
}

委托的實例實際就是代表你綁定的方法,把方法包裝成一個變量,Invoke時自動執行,這樣用委托實例的調用就和直接方法調用效果一樣。那這樣有什么用呢?用處太大了。這樣意味着你可以把一個方法當做參數傳遞給另一個方法,這么說好像也體會不到。類比下我們泛型的用途,泛型用於設計和類型無關的對象功能。那委托就是用於設計和方法無關的,可以將方法行為(邏輯)在從外部注入到對象內部。

3、委托意義

邏輯解耦,減少重復代碼

假如有以下集合List,Student有身高、年齡屬性。我們需要寫一個方法查詢出身高超過180cm的學生。(先不用List的Find、Where等方法,他們參數都是需要委托的,我們先考慮不用委托實現)。我們的方法可能像下面這樣

然后如果需要查詢出年齡大於20歲的呢,增加參數?方法重載?顯然都要破壞原有類的封裝。那我們想到“甩鍋”,把變化的邏輯甩給調用者。上面兩種查詢也只是student.Height>180和student.Age>20判斷邏輯的不同,可以把這部分當做參數傳遞進來。那么一組邏輯包裝成變量,這就是委托。一個方法委托了我,你調用我就是調用那個方法。改造后的方法像下面這樣

 

 

 

這樣我們只需要在外部調用的地方修改或增加相應的邏輯方法就可以。這里就相當於自己實現了List的Where擴展方法,這也是它的內部原理。

但這樣是不是也挺繁瑣的,還要定義委托、定義個方法,然后實例化委托,好繁瑣。既然委托規定了方法規范,那如果方法里不依賴的具體的類型,我們隨意指定方法的參數類型該多好,還記得上節說的泛型嗎。這里就使用泛型委托,我們也不自己去定義了,.NET為我們封裝了通用的兩類泛型委托Action<T>和Func<T>,前者代表無返回值,后者代表有返回值,每一類泛型委托都有十幾個泛型參數可以指定,絕對夠你用了。我們基本不用自己定義委托。這樣還不夠,我們還是要定義方法的呀。

代碼封裝,支持擴展

既然委托實例就是一個方法,結合泛型,那我們還可以做些更有趣的事情,下面實例代碼的左右就是給一個方法增加了異常處理。(這只是無返回值的,你也可以加一個有返回值的)。這樣就做到了,如果你的方法是指定給委托的,那么就可以捕獲異常,你大可以不在具體方法內處理異常(實際,還是老老實實處理,這只是最后一道門)。我們甚至可以在調用任何方法前后加上日志,而不用修改原類的封裝。有點類似AOP(面向切片編程)的味道了。

匿名方法和Lambda表達式

有時候簡單的邏輯我們不必編寫一個指定的方法。在實例化委托時可以直接指定一個匿名方法。像下面這樣。優點當然是減少代碼的復雜度了,還可以訪問匿名方法外部的變量,但匿名方法內部不能使用break,continue等跳轉語句。用來做簡單的邏輯。

在C#3.0開始,我們有了Lambda表達式代替匿名方法,它比匿名方法更加簡單。Lambda運算符“=>”(發音goesto)的左邊列出了需要的參數,右邊是利用該參數方法的實現代碼。如果表達式只有一個語句,還可以像圖二那樣簡寫。

異步多線程

異步多線程的實現都是基於委托的。開篇我們看到委托有BeginInvoke和EndInvoke,這里先簡單介紹下,我們會在后面異步多線程編程中詳細解讀。簡單的代碼如下圖所示,結果會發現BeginInvoke實際是啟用一個線程來調用方法的

BeginInvoke方法觸發你的異步方法,它和你想要執行的異步方法有相同的參數。另外還有兩個可選參數,第一個是AsyncCallback委托是異步完成的回調方法。第二個是用戶自定義對象,該對象將傳遞到回調方法中。BeginInvoke立即返回並且不等待完成異步的調用(繼續執行該下面的代碼,不需要等待)。BeginInvoke返回IAsyncResult接口,可用於檢測異步調用的過程。通過EndInvoke方法檢測異步調用的結果。如果異步調用尚未完成,EndInvoke將阻塞調用線程,直到它完成。

多播委托

前面使用的每個委托都只包含一個方法的調用。調用委托的次數和調用方法的次數相同。如果要調用多個方法,就需要多次顯示調用這個委托(就像多次調用一個方法一毛一樣)。委托也可以包含多個方法。這種委托稱為多播委托,但要注意如果方法有返回值,則只能得到委托調用最后一個方法的結果

使用運算符“+=”、“-=”來增加或去除委托的方法。+= 為委托實例按順序增加方法,形成方法鏈,Invoke時,按順序依次執行。-= 為委托實例移除方法,從方法鏈的尾部開始匹配,遇到第一個完全吻合的,移除且只移除一個,沒有也不異常。但有一點要注意,多播委托時是不能使用異步的。

三、事件

事件也幾乎無處不在,它提供一種發布/訂閱機制。我們做桌面開發,Button類提供的Click事件。觸發Click事件時調用的方法需要定義,其參數類型由委托類型定義。事件就是帶event關鍵字的委托的實例,event可以限制變量被外部調用/直接賦值。事件的標准用法如下,(當然你也可以自己定義更有意義的委托類型來代替Action)。

事件就是委托的實例,所以事件能干的事情,普通的委托實例也都能干。只是為了某些場景下,事件規范了使用方法。如,事件只能用過+=來注冊方法,只能在方法外部聲明在內部調用,普通委托實例多用於回調,而事件多用於外部接口。后面設計模式中的觀察者模式就是其典型應用。

四、總結

其實感覺理的不是很細,主要還是知識點的掃盲鞏固,再往細了去總結可能就會車軲轆話反反復復的了。
委托事件,我們平常會很常用的。特別是如果進階一下,異步多線程編程時會用的更多,沒有委托就沒有異步多線程。委托就是描述一類方法的類型,委托的實例就是代表一個方法。我們把一個方法當做一個委托的實例就可以進行傳遞,對於解耦有奇效。事件是委托的實例,最終是用來完成某一業務邏輯的一部分,只是這部分會變化,那么就把變化的形成封裝出去,交給上層來指定,通過事件可以提供一個供外部擴展動作的接口,這樣就會更加的靈活。規定動作我內部寫死,擴展的交給外部。

如果手機在手邊,也可以關注下vx:xishaobb,互動或獲取更多消息。當然這里也一直更新de。

 

 


免責聲明!

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



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