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

jiaxibei96發表於2019-10-26

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

一、概述

先說下委託,委託我們也經常用到。詳盡瞭解委託是必要的,不然在非同步多執行緒的程式設計中會一頭霧水。委託本質就是一個類,和我們平常定義的類沒多大區別。只是這個類的作用的是描述一些方法,沒有資料成員。一個委託定義了一類擁有同樣返回型別和引數的方法規範。委託的宣告語法就是一個沒有方法體的方法前面加上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。


相關文章