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條,因為有了匿名方法,從而可以在其中使用外部的一些變數,這樣就不必多建很多參數傳來傳去的了。