回調函數是一種非常有用的編程機制,它已經存在很多年了。Microsoft .NET Framework通過委托(delegate)來提供一種回調機制。不同於其他平台(比如非托管C++)的回調機制,委托提供了多得多的功能。例如,委托確保回調方法是類型安全的(這是CLR最重要的目標之一)。委托還允許順序調用多個方法,並支持調用靜態方法和實例方法。 為了理解委托,先看看如何使用它。 委托4個最基本的步驟: 1)定義委托類型 2)有一個方法包含要執行的代碼 3)創建一個委托實例化(包含聲明委托對象) 4)執行調用(invoke)委托實例 具體解釋如下: 1.定義委托類型 委托類型就是參數類型的一個列表以及一個返回類型。
StringProcessor( input);2.定義簽名相同的方法 定義的方法要與委托有類型相同的返回值和參數。
GetStringLength( x){}創建委托實例就是指定在調用委托實例時執行的方法。
proc1= proc2 += GetString;
4.調用委托 調用委托就是調用一個委托實例方法。
proc1();
具體的示例代碼:
StringProcessor( Person(.name = Say(+ + Note( Main(= Person(= Person( proc1 = = = proc1(書中的代碼示例:
Main( , , Counter(, , , , = Counter(, , Feedback fb1 = = = = ===, = (Feedback)Delegate.Remove(fbChain, , = = = = +=+=+=, -= , Counter(Int32 (Int32 val = ; val <= to; val++ (fb != + + = StreamWriter(, +在上面的代碼中,我們可以清楚的看到用委托如何回調靜態方法。直接將靜態方法綁定到委托的實例上,再通過實例進行調用。 將一個方法綁定到一個委托時,C#和CLR都允許引用類型的協變性和逆變性。協變性是指方法能返回從委托的返回類型派生的一個類型。逆變性是指方法獲取的參數可以是委托的參數類型的基類。例如下面的委托:
deleget Object MyCallback(FileStream s);
String SomeMethod(Stream s);注意,協變性和逆變性只能用於引用類型,不能作用於值類型和void。所以下面示例是錯誤的:
Int32 SomeMethod(Stream s);使用委托回調實例方法,在上面代碼中演示已經非常清楚了,就不細說了。 從表面看,委托似乎很容易使用:用C#的delegate關鍵字聲明,用熟悉的new操作符構造委托實例,用熟悉的方法調用語法來調用回調函數。 然而,實際情況遠比前面例子演示的復雜的多。編譯器和CLR在幕後做了大量工作來隱藏復雜性。本節重點講解了編譯器和CLR如何協同工作來實現委托。 首先讓我們重寫審視下面的代碼:
Feedback(Int32 value);
Feedback(Object IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object事實上,可用ILDasm.exe查看生成的程序集,驗證編譯器真的會自動生成這個類,如圖17-1所示:
Feedback fbStatic = = Feedback( Program.FeedbackToFile());
((d.Target != ) && d.Target.GetType() ==
(d.Method.Name ==
Counter(Int32 (Int32 val = ; val <= to; val++ (fb !=這段代碼看上去是在調用一個名為fb的函數,並向它傳遞一個參數(val)。但事實上,這裡沒有名為fb的函數。再次提醒你注意,因為編譯器知道fb是引用了一個委托對象的變量,所以會生成代碼調用該委托對象的Invoke方法。也就是說,編譯器看到以下代碼時:
fb(val);
fb.Invoke(val);
.method hidebysig Counter(int32 .maxstack ] CS$$
Counter(Int32 (Int32 val = ; val <= to; val++ (fb !=委托本身就已經相當有用了,在加上對委托鏈的支持,它的用處就更大了!委托鏈是由委托對象構成的一個集合。利用委托鏈,可調用集合中的委托所代表的全部方法。為了理解這一點,請參考第一節中的示例代碼中的ChainDelegateDemo1方法。在這個方法中,在Console.WriteLine語句之後,我構造了三個委托對象並讓變量fb1、fb2和fb3引用每一個對象,如圖17-3所示:
Feedback fbChain = = (Feedback)Delegate.Combine(fbChain, fb1);
fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
Counter(, , fbChain);
以偽代碼的方式,Feedback的Invoke的基本上是向下面這樣實現的:
= _invocationList (delegateSet != ( d }{
fbChain = (Feedback)Delegate.Remove(fbChain, Feedback(FeedbackToMsgBox));前面展示的例子中,委托返回值都是void。但是,完全可以向下面這樣定義Feedback委托:
Int32 Feedback (Int32 value);
= _invocationList (delegateSet != ( d = d(value); }{ result =
為方便C#開發人員,C#編譯器自動為委托類型的實例重載了+=和-=操作符。這些操作符分別調用了Delegate.Combine和Delegate.Remove。使用這些操作符,可簡化委托鏈的構造。
比如下面代碼:Feedback fbChain = +=+=+= fb3;現在我們已經理解了如何創建一個委托對象鏈,以及如何調用鏈中的所有對象。鏈中的所有項都會被調用,因為委托類型的Invoke方法包含了對數組中的所有項進行變量的代碼。因為Invoke方法中的算法就是遍歷,過於簡單,顯然,這有很大的局限性,除了最後一個返回值,其它所有回調方法的返回值都會被丟棄。還有嗎如果被調用的委托中有一個拋出一個或阻塞相當長的時間,我們又無能為力。顯然,這個算法還不夠健壯。 由於這個算法的局限,所以MulticastDelegate類提供了一個GetInvocationList,用於顯式調用鏈中的每一個委托,同時又可以自定義符合自己需要的任何算法:
下面是代碼演示:
InvalidOperationException( GetStatus getStatus = getStatus += GetStatus(+= GetStatus(+= GetStatus( (status == ) StringBuilder report = Delegate[] arrayOfDelegates = (GetStatus getStatus report.AppendFormat( Object component === ) ? : component.GetType() +The light is off Failed to get status from ConsoleTest.GetInvocationList+Fan.Speed Error: The fan broke due to overheating The volume is loud 許多年前,.NET Framework剛開始開發時,Microsoft引入委托的概念。開發人員在FCL中添加類時,他們在引入了回調方法的所有定法定義新的委托類型。隨著時間的推移,他們定義了太多的委托。事實上,現在僅僅在MSCorLib.dll中,就有接近50個委托類型。比如:
現在,.NET Framewoke現在支持泛型,所以實際上只需要幾個泛型委托就可以表示獲取多達16個參數的方法:
Action(); Action<T> Action<T1,T2> Action<T1,T2,T3> Action<T1,...,T16>(T1 obj1,...,T16 obj16);
TResult Func<TResult> TResult Func<T,TResult> TResult Func<T1,T2,TResult> TResult Func<T1,...,T16,TResult>(T1 arg1,...,T16 arg16);
Bar( Int32 z);許多開發人員認為和委托打交道很麻煩。因為它的語法很奇怪。例如以下代碼:
button1.Click += EventHandle(button1_Click);
}
button1_Click += button1_Click;如前所示,C#允許指定回調方法的名稱,不必構造一個委托對象包裝器。例如:
在前面的代碼中,是將回調方法SomeAsyncTask傳給ThreadPool的QueueUserWorkItem方法。C#允許我們以內聯的方式寫回調方法的代碼。不必再另外定義方法寫。例如,前面的代碼可以重寫為下面這樣:
=> Console.WriteLine(obj),
Int32[] squares = = AutoResetEvent( (Int32 n = ; n < squares.Length; n++= squares[num] = num * (Interlocked.Decrement( numToDo) == (Int32 n = ; n < squares.Length; n++注意:當lambda表達式造成編譯器生成一個類時,而且參數/局部變量被轉變成該類的字段後,變量引用的對象的生存周期被延長了。正常情況下,在方法中最後一次使用參數/局部變量之後,這個參數/局部變量就會"操作作用於",結束其生命周期。但是,將變量轉變成另一個類的字段後,只要包含字段的那個對象不"死",字段引用的對象也不會"死"。這在大多數應用程序中不是大的問題,但有時需要注意一下。 提示:毫無疑問,C#的lambda表達式功能很容易被開發人員濫用。我給自己設定了一個規則:如果需要在回調放方法中包含3行以上代碼,就不適用lambda表達式。相反的,我會手動寫一個方法,並為其分配一個自己的名稱。 到本節為止,為了使用委托,開發人員必須實現知道要回調的那個方法的原型。例如,如果fb是引用了一個Feedback委托的變量(第一節第二個示例程序),那麼為了調用這個委托,代碼應該這樣寫:
fb(item);不過在個別情況下,開發人員在編譯時並不知道這些信息。在11章"事件"討論EventSet類型時,曾經展示過一個例子。這個例子用一個字典來維護一組不同的委托類型。在運行時,為了引發事件,要在字典中查找並調用委托。但在編譯時,我們不能准確地知道要調用哪個委托,哪些參數必須傳給委托的回調方法。 System.Delegate提供了幾個方法。在編譯時不知道委托的這些必要信息時,可利用這個方法來創建並調用一個委托。以下是Dela\egate定義的這幾個方法:
Delegate CreateDelegate(Type type,Object firstArgument, MethodInfo method); Object DynamicInvoke(System.Delegate的DynamicInvoke方法允許調用委托對象的回調方法,傳遞一組在運行時確定的參數。調用DynamicInvoke時,它會在內部保證傳遞的參數與回調方法期望的參數兼容。如果兼容,就調用回調方法;否則拋出一個異常。DynamicInvoke返回回調方法所返回的對象。 下面代碼展示了如何使用CreateDelegate和DynamicInvoke方法:
(args.Length < == + + + + + + + + + + Type delType = Type.GetType(args[ (delType == + args[ MethodInfo mi = (Program).GetMethod(args[], BindingFlags.NonPublic | d = + args[ Object[] callbackArgs = Object[args.Length - (d.GetType() == (Int32 a = ; a < args.Length; a++- ] = (d.GetType() == Array.Copy(args, , callbackArgs, Object result = + n1 + n1 - =
Program.FeedbackToConsole