曾幾何時能看到微軟產品的源碼簡直是天方夜譚,不過現在這卻成了現實,微軟終於對外開放 了它的產品的源代碼.拋去開源運動與微軟之間的世代情仇,拋去微軟這一做法的初衷,這總歸是 件好事,能夠讓我們撥開雲霧,一窺優秀產品的秘密.
前兩天看到有位仁兄在隨筆中的留言,說他以為".NET中的設計模式"是在講.NET Framework 與設計模式的關系,其實不是,不過這也讓我想起來自己確實研究過.NET Framework的源碼,於是 就找打算找時間把自己的心得體會拿出來和大家一起分享.
今天就先從最容易讓人困惑的委托(delegate)開始,讓我們步入.NET Framework源碼世界,共 同學習優秀的程序設計.
先看委托的定義:用於聲明一個引用類型,該引用類型可用於封裝命名方法或匿名方法。委 托類似於 C++ 中的函數指針;但是,委托是類型安全和可靠的。
相信看到這段話之後,很多人,包括我自己就開始一起探索委托與函數指針,於是各種網文就 出現了.但委托到底是什麼呢?我們先看一段很簡單的代碼:
public delegate void OnAction(int flag);
這裡我們定義了一個最簡單的委托:OnAction.MSDN解釋Delegate 類是委托類型的基類,但只 有系統和編譯器可以顯式地從 Delegate 類或 MulticastDelegate 類派生.那麼我們可以認為 OnAction是從delegate繼承過來的,只是不能顯式的繼承,由系統代做了.
接下來讓我們看一下微軟是怎麼定義委托的:
[Serializable()]
[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].
下面看一下具體是怎麼實現委托的:
public abstract class Delegate : ICloneable, ISerializable
{
//要調用的對象
internal Object _target;
// MethodBase, either cached after first request or assigned from a DynamicMethod
internal MethodBase _methodBase;
// _methodPtr is a pointer to the method we will invoke
// It could be a small thunk if this is a static or UM call
internal IntPtr _methodPtr;
// In the case of a static method passed to a delegate, this field stores
// whatever _methodPtr would have stored: and _methodPtr points to a
// small thunk which removes the "this" pointer before going on
// to _methodPtrAux.
internal IntPtr _methodPtrAux;
// This constructor is called from the class generated by the
// compiler generated code
protected Delegate(Object target, String method)
{
if (target == null)
throw new ArgumentNullException("target");
if (method == null)
throw new ArgumentNullException("method");
// This API existed in v1/v1.1 and only expected to create closed
// instance delegates. Constrain the call to BindToMethodName to
// such and don't allow relaxed signature matching (which could make
// the choice of target method ambiguous) for backwards
// compatibility. The name matching was case sensitive and we
// preserve that as well.
if (!BindToMethodName(target, Type.GetTypeHandle(target), method,
DelegateBindingFlags.InstanceMethodOnly |
DelegateBindingFlags.ClosedDelegateOnly))
throw new ArgumentException(Environment.GetResourceString ("Arg_DlgtTargMeth"));
}
// This constructor is called from a class to generate a
// delegate based upon a static method name and the Type object
// for the class defining the method.
protected unsafe Delegate(Type target, String method)
{
if (target == null)
throw new ArgumentNullException("target");
if (!(target is RuntimeType))
throw new ArgumentException(Environment.GetResourceString ("Argument_MustBeRuntimeType"), "target");
if (target.IsGenericType && target.ContainsGenericParameters)
throw new ArgumentException(Environment.GetResourceString ("Arg_UnboundGenParam"), "target");
if (method == null)
throw new ArgumentNullException("method");
// This API existed in v1/v1.1 and only expected to create open
// static delegates. Constrain the call to BindToMethodName to such
// and don't allow relaxed signature matching (which could make the
// choice of target method ambiguous) for backwards compatibility.
// The name matching was case insensitive (no idea why this is
// different from the constructor above) and we preserve that as
// well.
BindToMethodName(null, target.TypeHandle, method,
DelegateBindingFlags.StaticMethodOnly |
DelegateBindingFlags.OpenDelegateOnly |
DelegateBindingFlags.CaselessMatching);
}
// Protect the default constructor so you can't build a delegate
private Delegate()
{
}
上面代碼顯示委托類包含4個internal類型的字段,從其注釋我們大致可以看出_target是我 們要調用的對象,_methodBase是給委托賦值時傳遞的方法,_methodPtr是指向該方法的指 針,_methodPtrAux同靜態類型委托的methodPtr指向的內容一樣,只是少了對象的引用. 在看委 托類的三個構造函數,一個是私有的,是用來防止實例化的,另外2個差不多,做的事情是把一個方 法綁定到一個對象上.這裡的方法應該是我們賦給委托的方法,對象則是編譯器自己來維護的對 象.這裡調用了一個方法BindToMethodName,在源碼中看不到實現,僅能看到如下內容:
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern bool BindToMethodName(Object target, RuntimeTypeHandle methodType, String method, DelegateBindingFlags flags);
MethodImplAttribute標簽表明了該方法為CLR內部方法,具體是什麼,我們不得而知.此類的 方法還有很多,就不一一例舉.說到這個地方,我們可以發現上面說的"OnAction是從delegate繼 承過來的"和"Delegate 類是委托類型的基類"並不正確,正確的理解應該是delegate類在內部管 理了一個用於綁定方法的對象,這點也可以從OnAction反匯編出來的構造函數看出:
.method public hidebysig specialname rtspecialname
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個重載,其方法體裡無一不包括:
Delegate d = InternalAlloc(type.TypeHandle);
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的一層外殼,並沒有涉及到很核心的內容.