1.Delegate是類型安全的,也就是說,在編譯期可以檢測出錯誤;而與之相似的Reflection是類型不 安全的。
Delegate是方法地址的指針,而且不區分static和instance方法。
Delegate是定義在Class之外的,這個平級的Class中包括Delegate要使用的方法。
2.Delegate允許引用類型的協變(covariance)和反協變(contra-variance)。即:
delegate object MyCallBack(FileStream fs); string SomeOtherMethod(Stream s) { }
可以將SomeOtherMethod方法綁定到MyCallBack委托上。
"協變"指對於返回類型,方法可以派生於委托。
"反協變"指對於參數類型,委托可以派生於方法。
3.委托的方法回調一般這樣寫:
void CallBack(string p1, string p2, TestDele d) { if (d != null) { d(p1, p2); } }
這裡我補充一句廢話:使用CallBack方法是可以傳入null值給TestDele參數的;CallBack方法中要判 斷d值是否為空,null就不能執行方法。
4.Delegate本質:一個類
以下委托聲明:
public delegate string TestDele(int intTest);
等價於這個類:
public class TestDele : System.MulticastDelete { public TestDele(int intTest, IntPtr method); public virtual string Invoke(int intTest); public virtual IAsyncResult BeginResult(int intTest, AsyncCallback callback, Object object); public virtual void EndInvoke(IAsyncResult result); }
委托的繼承關系:如圖
MulticastDelegete中有3個字段非常重要:
字段 類型 描述 _target System.Object 靜態方法時為null;實例方法時為實例對象。對外表現為屬性Target _methodPtr System.IntPtr 一個內部整數值,用來標志回調方法。對外表現為屬性Method,但此時已將整數轉換為 MemberInfo對象 _invocationList System.Object 通常是null,在“委托鏈”中形成一個數組因為_target和_methodPtr對外表現為屬性Target和Method,所以可以利用這個信息,
1)檢查一個委托是否引用一個特定類型的實例方法:
bool CheckDeleteTarget(MulticastDelegate d, Type type) { return ( (d.Target != null) && (d.Target.GetType() == type) ); }
2)檢查回調方法是否有特定的名稱:
bool CheckDeleteMethod(MulticastDelegate d, string methodName) { return (d.Method.Name = methodName) }
回到3.回調方法中
d(p1, p2); 等價於 d.Invoke(p1, p2),後者是這句話在委托類中的本質。
5.委托鏈回調多個方法
Delegate類提供兩個靜態方法Combine和Remove,
public static Delegate Combine(Delegate a, Delegate b); public static Delegate Remove(Delegate source, Delegate value);
使用如下:
FeedBack fbChain = null; FeedBack fb1 = new FeedBack(TestClass.Function); FeedBack fb2 = new FeedBack(TestClass.Function2); fbChain = (FeedBack)Delegate.Combine(fbChain, fb1); fbChain = (FeedBack)Delegate.Combine(fbChain, fb2);
只有一個委托時,_invocationList指向null;多於一個委托時形成委托鏈,產生一個委托對象數組, _invocationList會指向這個數組。
對於Remove方法,如果移除後委托鏈中不再有對象,則返回null,也就是說,_invocationList中允許 有一個元素的存在(不同於Combine處)
注:對於void型委托,委托鏈會按照隊列順序依次執行方法;對於有返回值的委托,委托鏈返回最後 一個委托的返回值。
在C#中,相應的提供+=與-=來代替Combine和Remove。
6.MulticastDelegate的GetInvocationList()實例方法
如果鏈中一個方法拋出異常,就會停止調用後續對象——這個算法不夠好。
為了避免這個問題,即使遇到異常,拋出後,繼續調用後續的委托方法,可以使用 GetInvocationList():
比如:delegateList為一個委托鏈,那麼通過delegateList.GetInvocationList()就可以得到一個委 托數組arrayDelegates,遍歷這個數組,在其中使用try...catch...捕獲異常,從而所有的委托方法都可 以調用到。
7. 委托中的反射——CreateDelegate()方法與DynamicInvoke()方法
使用委托,是因為在編譯時不知道要使用哪個回調方法,所以說委托是函數指針。
但如果在編譯時,連要使用哪個委托都不知道,或者不知道必須要傳遞哪些參數,這時候就需要反射 的幫助了。
Delegate提供兩個方法:
CreateDelegate(),有若干重載:
//For 靜態方法委托: public static Delegate CreateDelegate(Type type, MethodInfo method); public static Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure); //For 實例方法委托: public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method); public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure);
這裡type參數為具體delegate類型,method參數為回調方法名稱,如果有問題就根據 throwOnBindFailure判斷是否拋出異常,不拋出異常時就返回null;對於實例方法委托,還要額外傳入 firstArgument這個實例方法的對象。
可以通過反射獲取type參數和method參數,如下:
Type delType = Type.GetType("delegate類型");
以靜態方法為例:MethodInfo mi = typeof(靜態方法所在類).GetMethod("靜態方法名", BindingFlags.NonPublic | BindingFlags.Static)
DynamicInvoke(params Object[] args),實例方法,傳遞一組在運行時能確定的參數args。
這樣,偽代碼大致如下:
Delegate d; d.CreateDelegate(delType, mi); d.DynamicInvoke(params);
8.匿名方法——優雅的委托語法
書上說,使用匿名方法有一個尺度:如果回調方法中多於3行代碼,就不要使用。
我認為,不管幾行,我都不會去用——能看懂別人寫的匿名方法就可以。也許這就是BS程序員與CS程 序員的區別吧。
書中總結了4條,逐條分析:
第1條,直接使用回調函數作為參數,不需要構造委托對象
在下面的示例中,我們看到,SomeAsyncTask()方法作為參數傳遞,而 ThreadPool.QueueUserWorkItem()需要的是一個WaitCallback委托對象參數,.NET可以自動推斷,將方法 解析為委托。以下方法1和方法2其實是一樣的。
internal sealed class AClass { public static void CallbackWithoutNewingADelegateObject() { //方法1 WaitCallback waitCallback = new WaitCallback(SomeAsyncTask); ThreadPool.QueueUserWorkItem(waitCallback, 5); //方法2 ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5); } private static void SomeAsyncTask(Object o) { Console.WriteLine(o); } }
第2條,不需要定義回調方法——"匿名方法"由此而來
public static void CallbackWithoutNewingADelegateObject() { //方法3,於是可以省略SomeAsyncTask方法 ThreadPool.QueueUserWorkItem( delegate(Object obj) { Console.WriteLine(obj); }, 5); }
匿名方法中可以有任意行代碼,以上只是一行Console輸出語句
第3條,不需要指定回調方法的參數——不使用方法的參數時,可以省略參數部分
這一條是由第2條演變而來,如下示例:
button1.Click += delegate(Object sender, EventArgs e)
{
MessageBox.Show("Hello!");
};
//簡寫為
button1.Click += delegate
{
MessageBox.Show("Hello!");
};
因為sender和e兩個參數並不使用,所以省略之,但是CLR仍然會在編譯時自動生成這兩個參數。如果 匿名方法中使用了其中任一個參數,還是要全部聲明。
第4條,在回調方法中可以使用外部的變量
還是基於第2條,因為有了匿名方法,從而可以在其中使用外部的一些變量,這樣就不必多建很多參數 傳來傳去的了。