偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

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

對(duì)于委托大家并不陌生,但是對(duì)于.NET委托,在本文中作者還是不厭其煩的為大家進(jìn)行介紹。希望通過(guò)本文,能讓大家在使用.NET委托時(shí)得心應(yīng)手。

廢話

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

委托前世與今生

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

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

隱藏在背后的秘密

很簡(jiǎn)單的代碼吧,使用ILDasm反編譯一下:

wps_clip_image-0

奇怪的是,這么簡(jiǎn)單的一行代碼,變成了一個(gè)類(lèi):類(lèi)名與委托名一致,這個(gè)類(lèi)繼承自System.MulticastDelegate類(lèi),連構(gòu)造器一起有四個(gè)成員。看看我們?nèi)绾问褂眠@個(gè)委托:

  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看看結(jié)果:

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

發(fā)現(xiàn),.NET把委托就當(dāng)做一個(gè)類(lèi)型,與其他類(lèi)型一樣對(duì)待,現(xiàn)在你明白了上面那句話中說(shuō)委托是面向?qū)ο蟮暮瘮?shù)指針的意思了吧。

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

  1. .method public hidebysig instance void  AssignDelegate() cil managed     
  2. {    
  3.    // Code size       19 (0x13)     
  4.    .maxstack  8     
  5.  //將方法的第一個(gè)參數(shù)push到IL的運(yùn)算棧上(對(duì)于一個(gè)實(shí)例方法來(lái)說(shuō),比如AssignDelegate,它的第一個(gè)參數(shù)就是“this”了)    
  6.    IL_0000:  ldarg.0     
  7.  //這里又把this壓棧了一次,因?yàn)橄旅嬉粭l指令中的Test方法是一個(gè)實(shí)例方法,需要一個(gè)this     
  8.    IL_0001:  ldarg.0     
  9.  //ldftn就是把實(shí)現(xiàn)它的參數(shù)中的方法的本機(jī)代碼的非托管指針push到棧上,在這里你就可以認(rèn)為是獲取實(shí)例方法Test的地址    
  10.    IL_0002:  ldftn instance void Yuyijq.DotNet.Chapter2.TestDelegate::Test(int32)    
  11.  //調(diào)用委托的構(gòu)造器,這個(gè)構(gòu)造器需要兩個(gè)參數(shù),一個(gè)對(duì)象引用,就是第一次壓棧的this,一個(gè)方法的地址。    
  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.  } 

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

我們?cè)倩剡^(guò)頭來(lái)看看MyDelegate的繼承鏈:MyDelegate->MulticastDelegate->Delegate。

奇妙的地方

而Delegate中有三個(gè)有趣的字段:

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

對(duì)這三個(gè)字段做詳細(xì)說(shuō)明

_target

1、如果委托指向的方法是實(shí)例方法,則_target的值是指向目標(biāo)方法所在對(duì)象的指針

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

_methodPtr

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

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

_methodPtrAux

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

2、如果委托指向的是靜態(tài)方法,則這時(shí)_methodPtrAux起的作用與_mthodPtr在委托指向?qū)嵗椒ǖ臅r(shí)候是一樣的。

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

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


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

我們來(lái)自己動(dòng)手,分析一下上面的結(jié)論是否正確。

_target和_methodPtr真的如上面所說(shuō)的么?何不自己動(dòng)手看看。

建立一個(gè)Console類(lèi)型的工程,在項(xiàng)目屬性的“調(diào)試(Debug)”選項(xiàng)卡里選中“允許非托管代碼調(diào)試(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.  } 

上面是作為實(shí)驗(yàn)的代碼。

在CallByDelegate方法的第二行設(shè)置斷點(diǎn)

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

  1. //.load sos.dll用于加載SOS.dll擴(kuò)展  
  2. .load sos.dll  
  3. extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded  
  4. //Dump Stack Objects的縮寫(xiě),輸出棧中的所有對(duì)象  
  5. //該命令的輸出有三列,第二列Object就是該對(duì)象在內(nèi)存中的地址  
  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縮寫(xiě),參數(shù)為對(duì)象地址,輸出該對(duì)象的一些信息  
  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. //該對(duì)象的一些字段  
  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命令的輸出,這個(gè)不就是Yuyijq.DotNet.Chapter2.TestDelegate實(shí)例的內(nèi)存地址么。

在上面的!do命令的輸出中,我們看到了MethodTable:00263100,這就是該對(duì)象的方法表地址(關(guān)于方法表更詳細(xì)的討論會(huì)在后面的章節(jié)介紹到,現(xiàn)在你只要把他看做一個(gè)記錄對(duì)象所有方法的列表就行了,該列表里每一個(gè)條目就是一個(gè)方法)。現(xiàn)在我們要看看Yuyijq.DotNet.Chapter2.TestDelegate..Test方法的內(nèi)存地址,看起是否與_methodPtr的值是一致的,那么首先就要獲得Yuyijq.DotNet.Chapter2.TestDelegate.的實(shí)例中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,然后使用下面的命令找到Y(jié)uyijq.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這一列就是一個(gè)JIT Stub??纯?,果然與_methodPtr的是一致的,因?yàn)檫@時(shí)Test方法還沒(méi)有經(jīng)過(guò)JIT(JIT列為NONE),所以_methodPtr指向的是這里的JIT Stub。

如果給委托綁定一個(gè)靜態(tài)方法呢?現(xiàn)在我們把Test方法改為靜態(tài)的,那實(shí)例化委托的時(shí)候,就不能用this.Test了,而應(yīng)該用TestDelegate.Test。還是在原位置設(shè)置斷點(diǎn),使用與上面相同的命令,查看_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  

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

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

剛才不是說(shuō)這個(gè)時(shí)候_methodPtr指向的是一個(gè)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的方法的調(diào)用約定是Fast Call,對(duì)于Fast Call來(lái)說(shuō),方法的前兩個(gè)參數(shù)會(huì)放在ECX和EDX兩個(gè)寄存器中。那么mov eax,ecx實(shí)際上就是將_target傳遞給eax,再看看

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

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

通過(guò)委托調(diào)用方法

如何通過(guò)委托調(diào)用方法呢:

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

再來(lái)看看其對(duì)應(yīng)的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)熟悉,最關(guān)鍵的就是

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

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

多播的委托

好了,既然已經(jīng)解釋了面向?qū)ο蠛皖?lèi)型安全,那么說(shuō)委托是多播的咋解釋?zhuān)?/P>

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

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

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

.NET委托后記

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

原文標(biāo)題:探索.Net中的委托

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

【編輯推薦】

  1. C#委托實(shí)例簡(jiǎn)單分析
  2. 一個(gè).NET委托的故事:彼得,老板和宇宙
  3. 解惑答疑:C#委托和事件
  4. 各版本.NET委托的寫(xiě)法回顧
  5. 換一個(gè)角度看.NET中的理解委托和事件
責(zé)任編輯:彭凡 來(lái)源: 博客園
相關(guān)推薦

2010-01-25 18:05:40

C++語(yǔ)言

2009-08-05 17:04:14

.NET委托

2009-09-02 17:51:36

.NET委托

2009-09-08 16:25:19

C#委托

2009-08-18 11:08:24

.Net Framew

2025-02-27 00:37:06

2010-01-05 18:21:33

.NET Framew

2011-06-16 15:14:17

VB.NET事件委托

2010-08-03 08:52:23

委托接口

2011-05-20 16:33:47

委托接口

2009-08-10 09:41:07

.NET Lambda

2024-03-22 08:11:20

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

2009-09-14 18:41:59

LINQ查詢(xún)

2009-08-26 17:05:55

.NET委托

2009-09-15 15:07:25

2009-03-12 09:05:18

接口C#.NET

2023-08-17 14:10:11

Java開(kāi)發(fā)前端

2010-01-05 09:57:34

.NET Framew

2009-06-10 10:13:35

2011-05-06 16:19:18

網(wǎng)絡(luò)打印優(yōu)勢(shì)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)