程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [CLR via C#]17. 委托

[CLR via C#]17. 委托

編輯:C#入門知識

    回調函數是一種非常有用的編程機制,它已經存在很多年了。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的類,該類派生自FCL定義的System.MulticastDelegate類型(所有委托類型都派生自System.MulticastDelegate類型)。   提示:System.MulticastDelegate類派生自System.Delegate,後則又派生自System.Object。之所以有兩個委托類,是有歷史原因的。   從圖中可知Feedback的可訪問性是private,因為委托在源代碼中聲明為internal類。如果源代碼改成使用public可見性,編譯器生成的類也會是public類。要注意,委托類即可嵌套在一個類型中定義,也可以在全局范圍中定義。簡單地說,由於委托是類,所以凡是能夠定義類的地方,都能定義委托。   由於所有委托類型都派生自MulticastDelegate,所以它們繼承了MulticastDelegate的字段、屬性和方法。在這些成員中,有三個非公共字段是最重要的。    字段 類型 說明 _target System.Object 當委托對象包裝一個靜態方法時,這個字段為null。當委托對象包裝一個實例方法時,這個字段引用的是回調方法要操作的對象。換言之,這個字段指出了要傳給實例方法的隱式參數this的值 _methodPtr System.IntPtr 一個內部的整數值,CLR用它來標識要回調的方法 _invocationList System.Object 該字段通常為null。構造一個委托鏈時,它可以引用一個委托數組。     注意,所有委托都有一個構造器,它要獲取兩個參數:一個是對象引用,另一個是引用回調方法的一個整數。然而,如果仔細看下簽名的源代碼,會發現傳遞的是Program.FeedbackToConsole和di.FeedbackToFile這樣的值,這似乎不可能通過編譯吧?     然而,C#編譯器知道要構造的是委托,所以會分析源代碼來確定引用的是哪個對象和方法。對象引用被傳給構造器的object參數,標識了方法的一個特殊IntPtr值(從MethodDef或MemberRef元數據token獲得)被傳給構造器的method參數。對於靜態方法,會為object參數傳遞null值。在構造器內部,這兩個實參分別保存在_target和_methodPtr私有字段中。除此之外,構造器還將_invocationList字段設為null,對這個字段的討論推遲到後面。   所以,每個委托對象實際都是一個包裝器,其中包裝了一個方法和調用該方法時要操作的一個對象。例如,在執行以下兩行代碼之後:
    Feedback fbStatic = =  Feedback( Program.FeedbackToFile());

    Delegate類定義了兩個只讀的公共實例屬性:Target和Method。給定一個委托對象的引用,可查詢這些屬性。Target屬性返回一個引用,它指向回調方法要操作的對象。簡單的說,Target屬性返回保存在私有字段_target中的值。如果委托對象包裝的是一個靜態方法,Target將返回null。Method屬性返回一個System.Reflection.MethodInfo對象的引用,該對象標識了回調方法。簡單地說,Method屬性有一個內部轉換機制,能將私有字段_methodPtr中的值轉換為一個MethodInfo對象並返回它。   可通過多種方式利用這些屬性。例如,可檢查委托對象引用是不是一個特定類型中定義的實例方法:
 ((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,並打算讓它引用一個委托鏈或者一個委托對象集合,這些對象包裝了可以回調的方法。fbChain被初始化為null,表明目前沒有回調的方法。使用Delegate類的公共靜態方法Combine,可以將一個委托添加到鏈中:
Feedback fbChain = = (Feedback)Delegate.Combine(fbChain, fb1);

      為了在鏈中添加第二個委托,再次調用了Combine方法:
fbChain = (Feedback)Delegate.Combine(fbChain, fb2);

    為了在鏈中添加第三個委托,再次調用了Combine方法:
fbChain = (Feedback)Delegate.Combine(fbChain, fb3);

    在ChainDelegateDemo1方法中,用於設置委托鏈的所有代碼已經執行完畢,我將fnChain變量交給Counte方法:
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),

  編譯器選擇的方法名以<符號開頭,這是因為在C#中,標識符是不能包含<符號的;這就確保了你不會碰巧定義一個編譯器自動選擇的名稱。順便說一句,雖然C#禁止標識符包含<符號,但是CLR允許,這也就是為什麼編譯不會出錯的原因了。另外注意,雖然可將方法名作為一個字符串來傳遞,通過反射來訪問方法,但是C#語言規范指出,編譯器生成名稱的方式是沒有任何保證的。例如,每次編譯代碼,編譯器生成的方法都可能是一個不同的名稱。   通過ILDasm.exe,我們還注意到C#編譯器向這個方法應用了一個名為System.Runtime.CompilerServices.CompilerGeneratedAttribute的attribute,指出方法是編譯器生成的,而非開發人員定義的。=>操作符右側的代碼被放入這個編譯器生成的方法中。   提示:lambda表達式的主要優勢在於,它從你的源代碼中移除了一個"間接層"。   注意:C#2.0面世時,它引入了一個稱為匿名方法的功能。和C#3.0引入的lambda表達式相似,匿名方法描述的也是用於創建匿名函數的一個語法。C#語言規范建議開發人員使用新的lambda表達式,而不要使用舊的匿名方法語法,因為lambda表達式語法更簡潔,代碼更容易寫、讀和維護。     前面展示了回調代碼如何引用類中定義的其他成員。但有時候,還希望回調代碼引用存在於方法中的局部參數或變量。下面有個有趣的例子:
    
            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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved