.NET Framework源碼研究系列之---Delegate
前言
曾幾何時能看到微軟產品的源碼簡直是天方夜譚,不過現在這卻成了現實,微軟終于對外開放了它的產品的源代碼.拋去開源運動與微軟之間的世代情仇,拋去微軟這一做法的初衷,這總歸是件好事,能夠讓我們撥開云霧,一窺優秀產品的秘密.
前兩天看到有位仁兄在隨筆中的留言,說他以為".NET中的設計模式"是在講.NET Framework與設計模式的關系,其實不是,不過這也讓我想起來自己確實研究過.NET Framework的源碼,于是就找打算找時間把自己的心得體會拿出來和大家一起分享.
今天就先從最容易讓人困惑的委托(delegate)開始,讓我們步入.NET Framework源碼世界,共同學習優秀的程序設計.
先看委托的定義:用于聲明一個引用類 型,該引用類型可用于封裝命名方法或匿名方法。委托類似于 C++ 中的函數指針;但是,委托是類型安全和可靠的。
相信看到這段話之后,很多人,包括我自己就開始一起探索委托與函數指針,于是各種網文就出現了.但委托到底是什么呢?我們先看一段很簡單的代碼:
這里我們定義了一個最簡單的委托:OnAction.MSDN解釋Delegate 類是委托類型的基類,但只有系統和編譯器可以顯式地從 Delegate 類或 MulticastDelegate 類派生.那么我們可以認為OnAction是從delegate繼承過來的,只是不能顯式的繼承,由系統代做了.
接下來讓我們看一下微軟是怎么定義委托的:
[ClassInterface(ClassInterfaceType.AutoDual)]
[System.Runtime.InteropServices.ComVisible(true)]
public abstract class Delegate : ICloneable, ISerializable
由此可以看出delegate是個抽象類,并且實現了 ICloneable, ISerializable兩個接口,并且有ClassInterface(ClassInterfaceType.AutoDual)這么一個屬性.這有很多問題.
首先,委托是個抽象類,所以要使用的必須繼承,但是委托又跟整型一樣,是一種類型,由此就可以理解問什么"Delegate 類是委托類型的基類,但只有系統和編譯器可以顯式地從 Delegate 類或 MulticastDelegate 類派生"這句話的意思了,因為不可能從整型繼承過來一個子類,那么委托為什么是一個類而不像整型一樣是一個結構呢?這個問題下面回答.
其次,這也是我覺得不理解的地方,委托實現了ICloneable, ISerializable兩個接口,也就是說委托可以被克隆和序列化.相信大家沒人會寫OnAction.Clone();這么一句話.淺表克隆一個委托實在有點費解.不過,如果可以從良好的編程習慣上解釋為什么事先ICloneable接口的話,委托對ISerializable的實現就更讓人困惑了,因為可能因此導致一些莫名其妙的編譯時錯誤.我曾經遇到這樣一個Bug,一個標記序列化的單件實體類包含一個事件(事件默認實現了ISerializable),該事件導致序列化的時候在凡是跟該事件的引用有關的地方全部報出莫名其妙的未標記序列化的錯誤,最終的解決辦法是需要將該事件標記為[NonSerialized].
下面看一下具體是怎么實現委托的:
代碼
上面代碼顯示委托類包含4個internal類型的字段,從其注釋我們大致可以看出_target是我們要調用的對象,_methodBase是給委托賦值時傳遞的方法,_methodPtr是指向該方法的指針,_methodPtrAux同靜態類型委托的methodPtr指向的內容一樣,只是少了對象的引用. 在看委托類的三個構造函數,一個是私有的,是用來防止實例化的,另外2個差不多,做的事情是把一個方法綁定到一個對象上.這里的方法應該是我們賦給委托的方法,對象則是編譯器自己來維護的對象.這里調用了一個方法BindToMethodName,在源碼中看不到實現,僅能看到如下內容:
private extern bool BindToMethodName(Object target, RuntimeTypeHandle methodType, String method, DelegateBindingFlags flags);
MethodImplAttribute標簽表明了該方法為CLR內部方法,具體是什么,我們不得而知.此類的方法還有很多,就不一一例舉.說到這個地方,我們可以發現上面說的"OnAction是從delegate繼承過來的"和"Delegate 類是委托類型的基類"并不正確,正確的理解應該是delegate類在內部管理了一個用于綁定方法的對象,這點也可以從OnAction反匯編出來的構造函數看出:

instance void .ctor(object 'object',
native int 'method') runtime managed
{
} // end of method OnAction::.ctor
另外delegate中還有一個靜態方法:
public static Delegate CreateDelegate(Type type, Object firstArgument, MethodInfo method, bool throwOnBindFailure)
該方法前后有11個重載,其方法體里無一不包括:

if (!d.BindToMethodInfo(firstArgument, method.MethodHandle, method.DeclaringType.TypeHandle,
DelegateBindingFlags.RelaxedSignature))
{
if (throwOnBindFailure)
throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth"));
d = null;
}
這樣一段代碼.其中InternalAlloc,BindToMethodInfo與BindToMethodName一樣,都屬于CLR內部方法.具體是什么一樣看不出,只能大致猜測CLR內部生成了一個委托對象,然后將我們定義的委托簽名,方法,方法引用等一些內容交給委托類來維護.
通過以上分析,我們發現如它的定義,委托本質上是一個類,它的職責是代替我們管理維護方法調用.這一切無論從功能,還是外面,其實跟函數指針沒有半點關系.至此,相信大家對delegate有了不一樣的認識,同時也發現C#僅僅是CLR的一層外殼,并沒有涉及到很核心的內容.
以上僅僅是我個人的一點看法,由于本人水平有限,認識上難免有不正確的地方,如果你有更好的見解,敬請指正.