<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      代碼改變世界

      您真的了解類型轉(zhuǎn)換嗎?請止步,解惑!

      2011-08-29 00:11  空逸云  閱讀(2507)  評(píng)論(33)    收藏  舉報(bào)

      不久前,因?yàn)閷︻愋娃D(zhuǎn)換CLR的底層實(shí)現(xiàn)很朦朧,萬不得已下,發(fā)了一篇博文請園里的各位同學(xué),大大解惑。

      您真的了解類型轉(zhuǎn)換嗎?請止步,求解!

      很多熱心的園友紛紛發(fā)表了自己的意見和見解,在各位童鞋的幫助下,逐漸理清了類型轉(zhuǎn)換的內(nèi)幕(也可能并不是很正確!),于是想再整理一次,歡迎大家指正,而且也延發(fā)了其他的問題,想與大家一起討論。

      類型轉(zhuǎn)換的疑惑

      在上個(gè)問題中,我聲明了兩個(gè)類,父類Person,子類Employee,當(dāng)我實(shí)例化一個(gè)子類實(shí)例,并將其賦給父類的一個(gè)變量時(shí),我很好奇,且不了解明明是子類的實(shí)例,結(jié)果能識(shí)別到父類的方法,也就是為什么能知道是父類調(diào)用了方法。

      public class ClassConvert
      {
          public static void Main()
          {
              new ClassConvert().Run();
          }
      
          Employee kinsen;
          object obj;
          Person kong;
      
      
          public void Run()
          {
              kinsen = new Employee("Kinsen", "Chan");
              kinsen.SayHello();
              kong = kinsen;
              kong.SayHello();
              obj = kong;
      
              Console.ReadLine();
      
              Console.WriteLine(kinsen.GetType());
              Console.WriteLine(kong.GetType());
              Console.WriteLine(obj.GetType());
              Console.ReadLine();
          }
      }
      public class Person
      {
          public string FirstName { get; set; }
          public string LastName { get; set; }
          public Person(string firstname, string lastName)
          {
              this.FirstName = firstname;
              this.LastName = lastName;
          }
          public void SayHello()
          {
              Console.WriteLine("Hello,Word");
          }
      
          public override string ToString()
          {
              return FirstName + " " + LastName;
          }
      }
      public class Employee : Person
      {
          public Employee(string firstname, string lastname) : base(firstname, lastname) { }
          new public void SayHello()
          {
              Console.WriteLine("Hello,Word!My Name is " + base.FirstName);
          }
      

      運(yùn)行結(jié)果如下:

      image

      我們知道,引用類型主要數(shù)據(jù)信息是存放在托管堆中,而這塊內(nèi)存中包含三大塊,同步快,類型句柄已經(jīng)實(shí)例信息,對于類型轉(zhuǎn)換也僅僅是一個(gè)isinst或castclass指令(相見《.Net本質(zhì)論 79頁》)。最后把塊托管堆上的地址賦給線程棧上的變量,也就是說線程棧上的內(nèi)存僅僅保存了一個(gè)指向托管堆上的實(shí)例地址,而托管堆上僅有該實(shí)例的數(shù)據(jù),已經(jīng)類型句柄等數(shù)據(jù),其中類型句柄又指向該實(shí)例的具體方法實(shí)例,其中包含了方法表等類型信息。于是最后我們看到三個(gè)變量的GetType都是第一個(gè)New出來的對象。但是為什么調(diào)用的方法輸出卻不同,父類變量能正確的調(diào)用它的方法,這又是為什么呢?

      方法表與方法槽表

      這其中涉及到方法表與方法槽表等方面的知識(shí),然而關(guān)于這塊內(nèi)部的實(shí)現(xiàn),MSDN卻沒有什么官方資料,僅僅只能從一些MVP和開發(fā)者的筆下了解到這塊的存在。在上篇博文中,Anders Tan大哥對這方面做了一個(gè)解釋:

      方法表并不是指簡單的方法列表。它包含了很多的東西,它代表了一個(gè)class(不是class的實(shí)例對象),其中既有方法列表也有static成員等等,所以這也就解釋了為什么同一個(gè)class的實(shí)例中的static都是一樣的,因?yàn)閟tatic成員就存放在方法表中,而每個(gè)實(shí)例的type handle都指向自身class的方法表。接著方法表是一個(gè)包含很多元素的一個(gè)對象,所以在其中的方法列表也就是給了另外一個(gè)概念,就是方法槽表,在方法槽表中的方法也是按一定順序排列的。首先是父類的virtual方法,然后是自身的virtual方法,如果是override了父類的方法,那么父類的virtual方法就會(huì)被子類的方法所覆蓋(這也就解釋了在polymorphism下,向上轉(zhuǎn)型后調(diào)用virtual方法會(huì)執(zhí)行真正實(shí)例的方法),接著是實(shí)例方法和靜態(tài)方法。在方法槽后就是static成員。單是方法槽表本身并不包含其中各個(gè)方法的地址,我們知道.net程序在編譯后是IL代碼,但是在執(zhí)行的時(shí)候由JIT再編譯為本地代碼,那么在方法調(diào)用和方法體的關(guān)聯(lián)就會(huì)在執(zhí)行后發(fā)生變化,而這個(gè)變化并不在方法槽表中處理,而是交給了方法描述。

      《.Net本質(zhì)論》對這塊也有詳細(xì)的描述:

      方法表是一個(gè)帶有長度前綴的內(nèi)存地址數(shù)組,每個(gè)方法都有一個(gè)入口項(xiàng)。CLR方法表既包含實(shí)例來方法的入口,有包括靜態(tài)方法的入口。(《.Net本質(zhì)論》P155第一段倒數(shù)第三行,以下若無特別說明,則都摘自《.Net 本質(zhì)論》)

      CLR通過方法的聲明類型的方法表路由(route)所有的方法調(diào)用。(P155第二段)

      類型方法表的每個(gè)入口項(xiàng)指向一個(gè)唯一的存根例程(stub routine)。初始化時(shí),每個(gè)存根例程包含一個(gè)對于CLR的JIT編譯器的調(diào)用(它由內(nèi)部的PreStubWorker程序公開)P156第二段

      在這里,我并不打算深究方法表與方法槽表,僅僅是對于它們做一個(gè)簡單的介紹,了解它們是什么,好方便我進(jìn)一步的探究。

      那什么是方法槽表,本質(zhì)論中沒有對槽表做一個(gè)明確的定義,但是從描述中我們也可以“想象”出它該有的形象,方法槽表顧名思義,是一張表結(jié)構(gòu)的數(shù)據(jù)類型,可以將其想象成一排排的USB接口(槽口),槽口上插入(保存)的就是方法表上的偏移量(定位了方法表上的方法)。方法槽表上的順序首先是父類的virtual方法,然后是自身的virtual方法,再是自身的方法。

      多態(tài)方法表(槽表)的內(nèi)在模式

      CLR中,聲明一個(gè)方法,都會(huì)為該方法加上一個(gè)newslot標(biāo)記,表明這是一個(gè)新方法,若一個(gè)方法聲明為virtual且沒有標(biāo)明newslot標(biāo)記,那么CLR就將其看成是一個(gè)新方法,否則就看成是基類同名方法的重寫,如果標(biāo)明了newslot,那槽表上開辟多一個(gè)槽位來保存這個(gè)新方法,若虛方法沒有標(biāo)明newslot,則把方法槽表上相應(yīng)的“槽位”變成新方法的方法表偏移量,否則(沒有重寫虛方法),保存了基類方法的方法表偏移量。

      具體調(diào)用

      那么說了那么多,到底類型是怎么轉(zhuǎn)換的?原來,我都過多的把中心放在數(shù)據(jù)中(內(nèi)存),期望從托管堆,線程棧上找到什么。當(dāng)然,這注定失敗,我完全忽略了代碼的作用,畢竟,程序的執(zhí)行也就是逐步執(zhí)行代碼(指令/機(jī)器碼),上篇博文中qmxle童鞋提到IL的實(shí)現(xiàn),讓我醍醐灌頂,茅舍頓開。

      樓主,SayHello()方法不是虛方法的話,是在編譯時(shí)綁定的。看看IL代碼就明白了:
      IL_000b: newobj instance void ConsoleApplication15.Program/Employee::.ctor(string,
      string)
      IL_0010: stloc.0
      IL_0011: ldloc.0
      IL_0012: callvirt instance void ConsoleApplication15.Program/Employee::SayHello()
      IL_0017: nop
      IL_0018: ldloc.0
      IL_0019: stloc.1
      IL_001a: ldloc.1
      IL_001b: callvirt instance void ConsoleApplication15.Program/Person::SayHello()
      第一個(gè)SayHello()方法,綁定的是Employee類型;第二個(gè)SayHello()方法,綁定的是Person類型。

      這也就符合了《.Net本質(zhì)論》中的說法。

      當(dāng)從一個(gè)對象引用的類型轉(zhuǎn)換到另一個(gè)對象引用的類型時(shí),必須考慮兩個(gè)類型之間的關(guān)系。如果初始化引用的類型被認(rèn)定與新引用的類型兼容,那么,CLR所要做的轉(zhuǎn)換只是一個(gè)簡單的IA-32 mov指令。這通常出現(xiàn)于這樣的賦值情形中;當(dāng)一個(gè)派生類型的引用到一個(gè)直接或間接基類的引用,或則到一個(gè)一直兼容的接口引用。

      所以引用類型之間的類型轉(zhuǎn)換并不存在什么效率消耗的問題,它們之間的效率消耗僅僅在轉(zhuǎn)換之前做一個(gè)兼容性檢查時(shí)會(huì)消耗CPU時(shí)間,而不像裝箱,拆箱那樣很大的性能消耗。對于新類型的操作,就是依靠CPU指令(代碼)來識(shí)別了。看最后生成的IL:

      //省略前面...
        IL_000c:  newobj     instance void DebugTest.Employee::.ctor(string,
                                                                     string)
        IL_0011:  stfld      class DebugTest.Employee DebugTest.ClassConvert::kinsen
        IL_0016:  ldarg.0
        IL_0017:  ldfld      class DebugTest.Employee DebugTest.ClassConvert::kinsen
        IL_001c:  callvirt   instance void DebugTest.Employee::SayHello()
        IL_0021:  nop
        IL_0022:  ldarg.0
        IL_0023:  ldarg.0
        IL_0024:  ldfld      class DebugTest.Employee DebugTest.ClassConvert::kinsen
        IL_0029:  stfld      class DebugTest.Person DebugTest.ClassConvert::kong
        IL_002e:  ldarg.0
        IL_002f:  ldfld      class DebugTest.Person DebugTest.ClassConvert::kong
        IL_0034:  callvirt   instance void DebugTest.Person::SayHello()
       //省略后面...
      

      從上面可以看出,kinsen變量調(diào)用的是Employee的SayHello方法,而Kong變量調(diào)用的是Person的SayHello方法。這是因?yàn)?/p>

      SayHello方法不是虛方法,且Employee類對SayHello方法用了new關(guān)鍵字,CLR識(shí)別了它們不是同一個(gè)方法,但是這樣,我又引發(fā)了另一個(gè)問題。

      新問題!您知道嗎?

      從上面的IL中,可以看到,分別調(diào)用了Employee和Person類SayHello,下面,我們把SayHello改成虛方法,并重寫。

      public class Person
      {
          public string FirstName { get; set; }
          public string LastName { get; set; }
          public Person(string firstname, string lastName)
          {
              this.FirstName = firstname;
              this.LastName = lastName;
          }
          public virtual void SayHello()
          {
              Console.WriteLine("Hello,Word");
          }
      
          public override string ToString()
          {
              return FirstName + " " + LastName;
          }
      }
      public class Employee : Person
      {
          public Employee(string firstname, string lastname) : base(firstname, lastname) { }
          override public void SayHello()
          {
              Console.WriteLine("Hello,Word!My Name is " + base.FirstName);
          }
      }
      

      結(jié)果如下:

      image

      現(xiàn)在他們的輸出一樣了,我們再看看生成的IL。

        IL_000c:  newobj     instance void DebugTest.Employee::.ctor(string,
                                                                     string)
        IL_0011:  stfld      class DebugTest.Employee DebugTest.ClassConvert::kinsen
        IL_0016:  ldarg.0
        IL_0017:  ldfld      class DebugTest.Employee DebugTest.ClassConvert::kinsen
        IL_001c:  callvirt   instance void DebugTest.Person::SayHello()
        IL_0021:  nop
        IL_0022:  ldarg.0
        IL_0023:  ldarg.0
        IL_0024:  ldfld      class DebugTest.Employee DebugTest.ClassConvert::kinsen
        IL_0029:  stfld      class DebugTest.Person DebugTest.ClassConvert::kong
        IL_002e:  ldarg.0
        IL_002f:  ldfld      class DebugTest.Person DebugTest.ClassConvert::kong
        IL_0034:  callvirt   instance void DebugTest.Person::SayHello()
      

      現(xiàn)在,它們調(diào)用的都是Person類的SayHello了,為什么會(huì)變成Person呢?預(yù)想中應(yīng)該是Employee類的SayHello才對,如果調(diào)用

      Employee類的SayHello方法,那一切都能合理的解釋,但調(diào)用Person,程序是如何確定是Employee的SayHello方法呢?另外,我知道每個(gè)類型維護(hù)一張方法表,依稀記得一篇MSDN雜志上的文章說,如果在子類中找不到相關(guān)調(diào)用的方法,則會(huì)去父類的方法表中找,那么也就是子類和父類維護(hù)的方法表不一樣,子類的方法表中不會(huì)出現(xiàn)父類的方法?或許,在這里的調(diào)用程序如此,首先,會(huì)根據(jù)當(dāng)前實(shí)例instance找到類型句柄,定位到方法槽表,然后尋找槽表中匹配的方法,隨后調(diào)用?如果是如我猜想的這般,子類重寫的虛方法在方法槽表中的名稱還是父類方法的全名,而非子類的名稱?

      尾聲

      希望各位童鞋發(fā)表自己的見解,也希望各位大大能抒發(fā)所學(xué),不吝解答!對于上篇文章,很多童鞋都給出了自己的見解,給我理清概念有很大的幫助,在此很感謝大家,希望大家繼續(xù)發(fā)光發(fā)熱!:-)

      主站蜘蛛池模板: 日韩淫片毛片视频免费看| 精品国产色情一区二区三区| 久久精品国产亚洲av天海翼| 久久一日本道色综合久久| 99国精品午夜福利视频不卡99| 99久久夜色精品国产亚洲| 亚洲欧美精品一中文字幕| 尤物视频色版在线观看| 国产午夜精品理论大片| 18禁无遮拦无码国产在线播放| 国内精品久久人妻无码不卡| 免费视频欧美无人区码| 四虎国产精品永久免费网址| 久久夜色精品国产亚av| 久久国产精品老女人| 99久久无码一区人妻a黑| 国产成人精品无码片区在线观看| 日韩一区二区三区精彩视频| 国产福利深夜在线观看| 国内少妇偷人精品免费| 亚洲国产码专区在线观看| 九九热视频在线免费观看| 乱人伦人妻系列| 欧美大香线蕉线伊人久久| 九九热在线精品视频观看| 日本中文字幕有码在线视频| 国产乱码日韩亚洲精品成人| 7878成人国产在线观看| 亚洲国产一区二区三区亚瑟| japanese人妻中文字幕| 91在线视频视频在线| 国产粉嫩美女一区二区三| 国产亚洲精品超碰| 四虎成人在线观看免费| 精品国产一区二区三区国产区| 亚洲av无码牛牛影视在线二区| 亚洲成人一区二区av| 亚洲综合色区另类av| 亚洲首页一区任你躁xxxxx| 久久精品国产亚洲av麻豆不卡| 国产精品尤物午夜福利|