自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入探尋.NET委托的幾大秘密

開發(fā) 后端
我們這里將探索.NET委托的奧秘,希望本文能對大家深刻理解.NET下的委托有所幫助。

對于委托大家并不陌生,但是對于.NET委托,在本文中作者還是不厭其煩的為大家進行介紹。希望通過本文,能讓大家在使用.NET委托時得心應手。

廢話

我本來以為委托很簡單,本來只想簡簡單單的說說委托背后的東西,委托的使用方法。原本只想解釋一下那句:委托是面向對象的、類型安全的函數(shù)指針。可沒想到最后惹出一堆的事情來,越惹越多,罪過,罪過。本文后面一部分是我在一邊用SOS探索一邊記錄的,寫的非常糟糕,希望您的慧眼能發(fā)現(xiàn)一些有價值的東西,那我就感到無比的榮幸了。

委托前世與今生

大家可能還記得,在C/C++里,我們可以在一個函數(shù)里實現(xiàn)一個算法的骨架,然后在這個函數(shù)的參數(shù)里放一個“鉤子”,使用的時候,利用這個“鉤子”注入一個函數(shù),注入的函數(shù)實現(xiàn)不同算法的不同部分,這樣就可以達到算法骨架重用的目的。而這里所謂的“鉤子”就是“函數(shù)指針”。這個功能很強大啊,但是函數(shù)指針卻有它的劣勢:不是類型安全的、只能“鉤”一個函數(shù)。大家可能都知道微軟對委托的描述:委托是一種面向對象的,類型安全的,可以多播的函數(shù)指針。要理解這句話,我們先來看看用C#的關鍵字delegate聲明的一個委托到底是什么樣的東西:

  1. namespace Yuyijq.DotNet.Chapter2     
  2.  {    
  3.      public delegate void MyDelegate(int para);     
  4.  } 

隱藏在背后的秘密

很簡單的代碼吧,使用ILDasm反編譯一下:

wps_clip_image-0

奇怪的是,這么簡單的一行代碼,變成了一個類:類名與委托名一致,這個類繼承自System.MulticastDelegate類,連構造器一起有四個成員??纯次覀內绾问褂眠@個委托:

  1. public class TestDelegate     
  2.  {     
  3.     MyDelegate myDelegate;    
  4.       
  5.      public void AssignDelegate()    
  6.      {     
  7.          this.myDelegate = new MyDelegate(Test);    
  8.      }    
  9.  
  10.     public void Test(int para)    
  11.      {    
  12.          Console.WriteLine("Test Delegate");    
  13.      }    
  14.  } 

編譯后用ILDasm看看結果:

.field private class Yuyijq.DotNet.Chapter2.MyDelegate myDelegate

發(fā)現(xiàn),.NET把委托就當做一個類型,與其他類型一樣對待,現(xiàn)在你明白了上面那句話中說委托是面向對象的函數(shù)指針的意思了吧。

接著看看AssignDelegate反編譯后的代碼:

  1. .method public hidebysig instance void  AssignDelegate() cil managed     
  2. {    
  3.    // Code size       19 (0x13)     
  4.    .maxstack  8     
  5.  //將方法的第一個參數(shù)push到IL的運算棧上(對于一個實例方法來說,比如AssignDelegate,它的第一個參數(shù)就是“this”了)    
  6.    IL_0000:  ldarg.0     
  7.  //這里又把this壓棧了一次,因為下面一條指令中的Test方法是一個實例方法,需要一個this     
  8.    IL_0001:  ldarg.0     
  9.  //ldftn就是把實現(xiàn)它的參數(shù)中的方法的本機代碼的非托管指針push到棧上,在這里你就可以認為是獲取實例方法Test的地址    
  10.    IL_0002:  ldftn instance void Yuyijq.DotNet.Chapter2.TestDelegate::Test(int32)    
  11.  //調用委托的構造器,這個構造器需要兩個參數(shù),一個對象引用,就是第一次壓棧的this,一個方法的地址。    
  12.    IL_0008:  newobj instance void Yuyijq.DotNet.Chapter2.MyDelegate::.ctor(object,native int)    
  13.    IL_000d:  stfld class Yuyijq.DotNet.Chapter2.MyDelegate Yuyijq.DotNet.Chapter2.TestDelegate::myDelegate   
  14.    IL_0012:  ret    
  15.  } 

通過上面的代碼,我們會發(fā)現(xiàn),將一個實例方法分配給委托時,委托不僅僅引用了方法的地址,還有這個方法所在對象的引用,這里就是所謂的類型安全。

我們再回過頭來看看MyDelegate的繼承鏈:MyDelegate->MulticastDelegate->Delegate。

奇妙的地方

而Delegate中有三個有趣的字段:

  1. internal object _target;  
  2. internal IntPtr _methodPtr;  
  3. internal IntPtr _methodPtrAux; 

對這三個字段做詳細說明

_target

1、如果委托指向的方法是實例方法,則_target的值是指向目標方法所在對象的指針

2、如果委托指向的是靜態(tài)方法,則_target的值是委托實例自身

_methodPtr

1、如果委托指向的方法是實例方法,則_methodPtr的值指向一個JIT Stub(如果這個方法還沒有被JIT編譯,關于JIT Stub會在后面的章節(jié)介紹),或指向該方法JIT后的地址

2、如果委托指向的方法是靜態(tài)方法,則_methodPtr指向的是一個Stub(一段小代碼,這段代碼的作用是_target,然后調用_methodPtrAux指向的方法),而且所有簽名相同的委托,共享這個Stub。為什么要這樣一個Stub?我想是為了讓通過委托調用方法的流程一致吧,不管指向的是實例方法還是靜態(tài)方法,對于外部來說,只需要調用_methodPtr指向的地址,但是對于調用實例方法而言,它需要this,也就是這里的_target,而靜態(tài)方法不需要,為了讓這里的過程一直,CLR會偷偷的在委托指向靜態(tài)方法時插入一小段代碼,用于去掉_target,而直接jmp到_methodPtrAux指向的方法。

_methodPtrAux

1、如果委托指向的是實例方法,則_methodPtrAux就是0。

2、如果委托指向的是靜態(tài)方法,則這時_methodPtrAux起的作用與_mthodPtr在委托指向實例方法的時候是一樣的。

實際上通過反編譯Delegate的代碼發(fā)現(xiàn),Delegate有一個只讀屬性Target,該Target的實現(xiàn)依靠GetTarget方法,該方法的代碼如下:

  1. internal virtual object GetTarget()     
  2.  {     
  3.      if (!this._methodPtrAux.IsNull())     
  4.      {     
  5.         return null;     
  6.      }     
  7.      return this._target;     
  8.  } 


實了當委托指向靜態(tài)方法時,Target屬性為null。

我們來自己動手,分析一下上面的結論是否正確。

_target和_methodPtr真的如上面所說的么?何不自己動手看看。

建立一個Console類型的工程,在項目屬性的“調試(Debug)”選項卡里選中“允許非托管代碼調試(Enable unmanaged code debuging)”。 

  1. namespace Yuyijq.DotNet.Chapter2     
  2.  {     
  3.      public delegate void MyDelegate(int para);     
  4.      public class TestDelegate     
  5.     {     
  6.          public void Test(int para)     
  7.          {    
  8.              Console.WriteLine("Test Delegate");    
  9.          }    
  10.          public void CallByDelegate()    
  11.          {    
  12.              MyDelegate myDelegate = new MyDelegate(this.Test);    
  13.             myDelegate(5);    
  14.          }    
  15.       
  16.          static void Main()    
  17.          {    
  18.             TestDelegate test = new TestDelegate();    
  19.              test.CallByDelegate();    
  20.         }    
  21.      }   
  22.  } 

上面是作為實驗的代碼。

在CallByDelegate方法的第二行設置斷點

F5執(zhí)行,命中斷電后,在Visual Studio的立即窗口(Immediate Window)里輸入如下命令(菜單欄->調試(Debug)->立即窗口(Immediate)):

  1. //.load sos.dll用于加載SOS.dll擴展  
  2. .load sos.dll  
  3. extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded  
  4. //Dump Stack Objects的縮寫,輸出棧中的所有對象  
  5. //該命令的輸出有三列,第二列Object就是該對象在內存中的地址  
  6. !dso  
  7. PDB symbol for mscorwks.dll not loaded  
  8. OS Thread Id: 0x1588 (5512)  
  9. ESP/REG Object Name  
  10. 0037ec10 019928a4 Yuyijq.DotNet.Chapter2.TestDelegate  
  11. 0037ed50 019928a4 Yuyijq.DotNet.Chapter2.TestDelegate  
  12. 0037ed5c 019928b0 Yuyijq.DotNet.Chapter2.MyDelegate  
  13. 0037ed60 019928b0 Yuyijq.DotNet.Chapter2.MyDelegate  
  14. 0037ef94 019928b0 Yuyijq.DotNet.Chapter2.MyDelegate  
  15. 0037ef98 019928b0 Yuyijq.DotNet.Chapter2.MyDelegate  
  16. 0037ef9c 019928a4 Yuyijq.DotNet.Chapter2.TestDelegate  
  17. 0037efe0 019928a4 Yuyijq.DotNet.Chapter2.TestDelegate  
  18. 0037efe4 019928a4 Yuyijq.DotNet.Chapter2.TestDelegate  
  19. //do命令為Dump Objects縮寫,參數(shù)為對象地址,輸出該對象的一些信息  
  20. !do 019928b0  
  21. Name: Yuyijq.DotNet.Chapter2.MyDelegate  
  22. MethodTable: 00263100  
  23. EEClass: 002617e8  
  24. Size: 32(0x20) bytes  
  25. (E:\Study\Demo\Demo\bin\Debug\Demo.exe)  
  26. //該對象的一些字段  
  27. Fields:  
  28. MT Field Offset Type VT Attr Value Name  
  29. 704b84dc 40000ff 4 System.Object 0 instance 019928a4 _target  
  30. 704bd0ac 4000100 8 ...ection.MethodBase 0 instance 00000000 _methodBase  
  31. 704bb188 4000101 c System.IntPtr 1 instance 0026C018 _methodPtr  
  32. 704bb188 4000102 10 System.IntPtr 1 instance 00000000 _methodPtrAux  
  33. 704b84dc 400010c 14 System.Object 0 instance 00000000 _invocationList  
  34. 704bb188 400010d 18 System.IntPtr 1 instance 00000000 _invocationCount 

在最后Fields一部分,我們看到了_target喝_methodPtr,_target的值為019928a4,看看上面!dso命令的輸出,這個不就是Yuyijq.DotNet.Chapter2.TestDelegate實例的內存地址么。

在上面的!do命令的輸出中,我們看到了MethodTable:00263100,這就是該對象的方法表地址(關于方法表更詳細的討論會在后面的章節(jié)介紹到,現(xiàn)在你只要把他看做一個記錄對象所有方法的列表就行了,該列表里每一個條目就是一個方法)?,F(xiàn)在我們要看看Yuyijq.DotNet.Chapter2.TestDelegate..Test方法的內存地址,看起是否與_methodPtr的值是一致的,那么首先就要獲得Yuyijq.DotNet.Chapter2.TestDelegate.的實例中MethodTable的值:

  1. !do 019928a4  
  2. Name: Yuyijq.DotNet.Chapter2.TestDelegate  
  3. MethodTable: 00263048  
  4. EEClass: 002612f8  
  5. Size: 12(0xc) bytes  
  6. (E:\Study\Demo\Demo\bin\Debug\Demo.exe)  
  7. Fields:  
  8. None  

現(xiàn)在知道了其方法表的值為00263048,然后使用下面的命令找到Yuyijq.DotNet.Chapter2.TestDelegate..Test方法的地址:

  1. !dumpmt -md 00263048  
  2. EEClass: 002612f8  
  3. Module: 00262c5c  
  4. Name: Yuyijq.DotNet.Chapter2.TestDelegate  
  5. mdToken: 02000003 (E:\Study\Demo\Demo\bin\Debug\Demo.exe)  
  6. BaseSize: 0xc  
  7. ComponentSize: 0x0  
  8. Number of IFaces in IFaceMap: 0  
  9. Slots in VTable: 9  
  10. --------------------------------------  
  11. MethodDesc Table  
  12. Entry MethodDesc JIT Name  
  13. .......  
  14. 0026c010 00262ffc NONE Yuyijq.DotNet.Chapter2.TestDelegate.AssignDelegate()  
  15. 0026c018 0026300c NONE Yuyijq.DotNet.Chapter2.TestDelegate.Test(Int32)  
  16. ...... 

Entry這一列就是一個JIT Stub??纯?,果然與_methodPtr的是一致的,因為這時Test方法還沒有經(jīng)過JIT(JIT列為NONE),所以_methodPtr指向的是這里的JIT Stub。

如果給委托綁定一個靜態(tài)方法呢?現(xiàn)在我們把Test方法改為靜態(tài)的,那實例化委托的時候,就不能用this.Test了,而應該用TestDelegate.Test。還是在原位置設置斷點,使用與上面相同的命令,查看_target與_methodPtr的值。

  1. MT Field Offset Type VT Attr Value Name  
  2. 704b84dc 40000ff 4 System.Object 0 instance 01e928b0 _target  
  3. 704bb188 4000101 c System.IntPtr 1 instance 007809C4 _methodPtr  
  4. 704bb188 4000102 10 System.IntPtr 1 instance 0025C018 _methodPtrAux  

你會發(fā)現(xiàn)這里的_target字段的值就是MyDelegate的實例myDelegate的地址。然后我們通過上面的方法,找到Test方法的地址,發(fā)現(xiàn)_methodPtrAux的值與該值是相同的。

實際上你還可以再編寫一個與MyDelegate相同簽名的委托,然后也指向一個靜態(tài)方法,使用相同的方法查看該委托的_methodPtr的值,你會發(fā)現(xiàn)這個新委托與MyDelegate的_methodPtr的值是一致的。

剛才不是說這個時候_methodPtr指向的是一個Stub么,既然如此那我們反匯編一下代碼:

  1. !u 007809C4  
  2. Unmanaged code  
  3. 007809C4 8BC1 mov eax,ecx  
  4. 007809C6 8BCA mov ecx,edx  
  5. 007809C8 83C010 add eax,10h  
  6. 007809CB FF20 jmp dword ptr [eax]  
  7. ........ 

.Net里JIT的方法的調用約定是Fast Call,對于Fast Call來說,方法的前兩個參數(shù)會放在ECX和EDX兩個寄存器中。那么mov eax,ecx實際上就是將_target傳遞給eax,再看看

704bb188 4000102 10 System.IntPtr 1 instance 0025C018 _methodPtrAux

_methodPtrAux的偏移是10,這里的add eax,10h就是將eax指向_methodPtrAux,然后jmp dword ptr[eax]就是跳轉到_methodPtrAux所指向的地址了,就是委托指向的那個靜態(tài)方法。

通過委托調用方法

如何通過委托調用方法呢:

  1.  public void CallByDelegate()    
  2.     
  3.    MyDelegate myDelegate = new MyDelegate(this.Test);     
  4.    myDelegate(5);    

再來看看其對應的IL代碼:

  1. .method public hidebysig instance void  CallByDelegate() cil managed     
  2. {      // Code size       21 (0x15)     
  3.    .maxstack  3    
  4.    .locals init ([0] class Yuyijq.DotNet.Chapter2.MyDelegate myDelegate)     
  5.    IL_0000:  ldarg.0     
  6.    IL_0001:  ldftn instance void Yuyijq.DotNet.Chapter2.TestDelegate::Test(int32)     
  7.    IL_0007:  newobj instance void Yuyijq.DotNet.Chapter2.MyDelegate::.ctor(object, native int)     
  8.    IL_000c:  stloc.0    
  9.    IL_000d:  ldloc.0    
  10.    IL_000e:  ldc.i4.5    
  11.    IL_000f:  callvirt   instance void Yuyijq.DotNet.Chapter2.MyDelegate::Invoke(int32)    
  12.    IL_0014:  ret    
  13.  } 

前面的代碼我們已經(jīng)熟悉,最關鍵的就是

  1. callvirt instance void Yuyijq.DotNet.Chapter2.MyDelegate::Invoke(int32) 

我們發(fā)現(xiàn),通過委托調用方法,實際上就是調用委托的Invoke方法。

多播的委托

好了,既然已經(jīng)解釋了面向對象和類型安全,那么說委托是多播的咋解釋?

你可能已經(jīng)發(fā)現(xiàn),MyDelegate繼承自MulticastDelegate,看這個名字貌似有點意思了。來看看下面這兩行代碼:

  1. MyDelegate myDelegate = new MyDelegate(this.Test);     
  2. myDelegate += new MyDelegate(this.Test1); 

通過IL我們可以發(fā)現(xiàn),這里的+=最后就是調用System.Delegate的Combine方法。而Combine的真正實現(xiàn)時在MulticastDelegate的CombineImpl方法中。在MulticastDelegate中有一個_invocationList字段,從CombineImpl中可以看出這個字段是一個object[]類型的,而委托鏈就放在這個數(shù)組里。

.NET委托后記

文章是想到哪兒寫到哪兒,寫的比較亂,也比較匆忙。非常抱歉。對于中間那段奇妙的事情,我原來真的不知道,我一直以為當委托指向一個靜態(tài)方法時,_target指向null就完事兒了,沒想到還有這么一番景象。看來很多東西還是不能想當然,親身嘗試一下才知道真實的情況。

原文標題:探索.Net中的委托

鏈接:http://www.cnblogs.com/yuyijq/archive/2009/10/14/1583251.html

【編輯推薦】

  1. C#委托實例簡單分析
  2. 一個.NET委托的故事:彼得,老板和宇宙
  3. 解惑答疑:C#委托和事件
  4. 各版本.NET委托的寫法回顧
  5. 換一個角度看.NET中的理解委托和事件
責任編輯:彭凡 來源: 博客園
相關推薦

2010-01-25 18:05:40

C++語言

2009-08-18 11:08:24

.Net Framew

2009-09-02 17:51:36

.NET委托

2009-08-05 17:04:14

.NET委托

2009-09-08 16:25:19

C#委托

2025-02-27 00:37:06

2011-06-16 15:14:17

VB.NET事件委托

2010-01-05 18:21:33

.NET Framew

2010-08-03 08:52:23

委托接口

2011-05-20 16:33:47

委托接口

2009-08-10 09:41:07

.NET Lambda

2009-03-12 09:05:18

接口C#.NET

2009-08-26 17:05:55

.NET委托

2024-03-22 08:11:20

.NETJSON數(shù)據(jù)序列化

2010-01-05 09:57:34

.NET Framew

2009-09-14 18:41:59

LINQ查詢

2009-06-10 10:13:35

2010-02-26 15:22:55

.NET Framew

2023-08-17 14:10:11

Java開發(fā)前端

2011-05-06 16:19:18

網(wǎng)絡打印優(yōu)勢
點贊
收藏

51CTO技術棧公眾號