天天看點

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

一直以來都是對于事件與委托比較混淆,而且不太會用。找了個時間,總結了一下,感覺清晰了很多。

先說一下個人了解的結論吧:

      delegate是c#中的一種類型,它實際上是一個能夠持有對某個方法的引用的類。

     delegate聲明的變量與delegate聲明的事件,并沒有本質的差別,事件是在delegate聲明變量的基礎上包裝而成的,類似于變量與屬性的關系(在il代碼中可以看到每一個delegate聲明的事件都對應是私有的delegate聲明的變量),提升了安全性。

      action 與func:這兩個其實說白了就是系統定義好的delegate,他有很多重載的方法,便于各種應用情況下的調用。他在系統的system命名空間下,是以全局可見。

首先了解一下, ildasm中圖示含義:  

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

委托建立步驟:

1、用delegate關鍵字建立一個委托,包括聲明傳回值和參數類型

2、使用的地方接收這個委托

3、建立這個委托的執行個體并指定一個傳回值和參數類型比對的方法傳遞過去

一、事件與委托

建立一個事件委托測試項目:eventdelegatetest

具體代碼如下:

編譯代碼後,使用 visual studio 2010自帶的ildasm.exe:

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

打開該dll,可以看到如下資訊:

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

從上圖可以看出如下幾點資訊:

1、委托 public delegate int delegateaction();

      在il中是以類(delegateaction)的形式存在的

      .net将委托定義為一個密封類,派生自基類system.multicastdelegate,并繼承了基類的三個方法

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

2、public event delegateaction onactionevent;

      在il中不僅僅對應event onactionevent而且還對應一個field onactionevent;

而field onactionevent與 public delegateaction danew生成的field danew是一樣的

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系
通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

都是以字段(field )的形式存在的。

輕按兩下event onactionevent可以看到如下資訊:

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

在il中事件被封裝成了包含一個add_字首和一個remove_字首的的代碼段。

其中,add_字首的方法其實是通過調用delegate.combine()方法來實作的,組成了一個多點傳播委托;remove_就是調用delegate.remove()方法,用于移除多點傳播委托中的某個委托。

也就是說:事件其實就是一個特殊的多點傳播委托

那麼對于事件進行這一次封裝有什麼好處呢?

1、因為delegate可以支援的操作非常多,比如我們可以寫onxxxchanged += aaafunc,把某個函數指針挂載到這個委托上面,但是我們也可以簡單粗暴地直接寫onxxxchanged = aaafunc,讓這個委托隻包含這一個函數指針。不過這樣一來會産生一個安全問題:如果我們用onxxxchanged = aaafunc這樣的寫法,那麼會把這個委托已擁有的其他函數指針給覆寫掉,這大概不是定義onxxxchanged的程式員想要看到的結果。

小注:

    雖然事件不能直接=某個函數,也不可以直接=null

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

2、還有一個問題就是onxxxchanged這個委托應該什麼時候觸發(即調用它所包含的函數指針)。從面向對象的角度來說,xxx改變了這個事實(即onxxxchaned的字面含義)應該由包含它的那個對象來決定。但實際上我們可以從這個對象的外部環境調用onxxxchanged,這既産生了安全問題也不符合面向對象的初衷。 

說到這裡對于事件與委托的管理算是說明白了,那麼平時常用的action與func,與委托又有什麼關系呢?

二、action 與func

action 委托:封裝一個方法,該方法具有參數(0到16個參數)并且不傳回值。

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

func<t, tresult> 委托:封裝一個具有參數(0到16個參數)并傳回 tresult 參數指定的類型值的方法。

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

那麼這action與func是怎麼實作的呢?

1、action(以action<t1, t2> 委托:封裝一個方法,該方法具有兩個參數并且不傳回值為例)

從微軟公布的源碼中,可以看到,如下實作:

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

上面這個聲明就是:該方法具有兩個參數并且不傳回值的委托。

其餘使用方式與委托變量一樣。

2、func(以func<t1, t2, tresult> 委托:封裝一個具有兩個參數并傳回 tresult 參數指定的類型值的方法為例)

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

此處,可以看出func與action是類似的,唯一的差別就是,func必須指定傳回值的類型,使用方式與委托咱們自己使用委托變量是一樣的,直接使用相應參數的func或者action聲明變量,=或者+=挂載函數(方法即可)

這兩個其實說白了就是系統定義好的delegate,他有很多重載的方法,便于各種應用情況下的調用。他在系統的system命名空間下,是以全局可見。

三、predicate

是傳回bool型的泛型委托,predicate有且隻有一個參數,傳回值固定為bool。表示定義一組條件并确定指定對象是否符合這些條件的方法。此方法常在集合(array 和 list<t>)的查找中被用到,如:數組,正則拼配的結果集中被用到。

具體用法demo如下:

上例中說明了predicate的使用,findall方法中,參數2即是一個predicate,在具體的執行中,每一個數組的元素都會執行指定的方法,如果滿足要求傳回true,并會被存放在結果集中,不符合的則被剔除,最終傳回的集合,即是結果判斷後想要的集合。

以上代碼執行結果為:

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

那麼predicate<t>與委托又有什麼關系呢?

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系

從微軟源碼中可以看出predicate<t>是傳回bool型的泛型委托,從本質上來說與func、action、事件、委托變量并無本質差別。

通過IL分析C#中的委托、事件、Func、Action、Predicate之間的差別與聯系