您真的了解類型轉換嗎?請止步,求解!
2011-08-24 23:57 空逸云 閱讀(4675) 評論(100) 收藏 舉報前陣子,一名同事問及類型轉換的問題,我也僅僅說出目前自己的了解。但回頭想想,其中的確大有學問,以前只看到了表面,其內在的表現如何,苦苦翻書,Google幾番之后,依然無所收獲,故大膽寫下,求園中各位大牛不吝解答。
類型轉換的疑惑
首先,我們知道類型轉換也就那點事(表面的說),總歸而言,C#下有幾種轉換,裝箱,拆箱,向上類型轉換,向下類型轉換,平行類型轉換幾種。這幾種的區別目前也不細說了,感興趣的童鞋可移步C# 裝箱和拆箱[整理],向上類型轉換,向下類型轉換,平行類型(.Net本質論79頁-運行時的類型)。
依照以往的知識,現在我們假設有一個Person類,再有一個Employee類,Employee繼承Person,聲明一個Employee的實例,并將其賦給一個Person的實例,由于類型是引用類型,則實際上它們都是指向同一個對象實例。代碼如下:
Employee kinsen = new Employee("Kinsen", "Chan"); Person kong = kinsen;
這一點應該是毫無疑問的。問題是,已知在構造一個實例的時候,實際上是在堆棧上開辟一塊空間,這塊空間包含三塊,分別是同步
塊,類型句柄,以及實例具體信息。我們就是通過類型句柄來獲得該實例的具體對象。但此時Person類實例kong指向的是kinsen實例的地址,那么該類型句柄的信息也應該是Employee而非Person的。但偏偏我們卻能正確的獲取到Person的方法,也能正確的執行,看一下代碼
public class Program { static void Main(string[] args) { Employee kinsen = new Employee("Kinsen", "Chan"); kinsen.SayHello(); Person kong = kinsen; kong.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 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); } } }
執行結果如下:
在這里我并沒有采用虛方法,否則結果都是第一個了。可見,即使kong引用的是kinsen,但實際上它執行的還是Person類的方法。那么,到底是從哪里得知kong是Person類對象的呢?再見一個實驗。
Employee kinsen = new Employee("Kinsen", "Chan"); kinsen.SayHello(); Person kong = kinsen; kong.SayHello(); object obj = kong; Console.WriteLine(kinsen.GetType()); Console.WriteLine(kong.GetType()); Console.WriteLine(obj.GetType());
除了Employee和Person類實例,我們還將kong賦給了一個object,然后輸出實例的類型,結果如下:
可以看到,三個實例的實際類型都是Employee,但是Person類實例的確是執行了Person類的SayHello方法啊。這到底是為什么?
到處尋找答案,在《.Net本質論》79頁中找到這么一段話:
當從一個對象引用的類型轉換到另一個對象引用的類型時,必須考慮兩個類型之間的關系。如果初始化引用的類型被認定與新引用的類型兼容,那么,CLR所要做的轉換只是一個簡單的IA-32 mov指令。這通常出現于這樣的賦值情形中;當一個派生類型的引用到一個直接或間接基類的引用,或則到一個一直兼容的接口引用。
從這段話中,了解到為什么結果三個實例的類型都是Employee,但我想解決的問題還沒解決,為何指向Employee實例引用的Person類實例還能準確的找到它的類型呢?目前我已知的信息如下:
實例地址,也就是線程棧上的地址,它只包含一個指向堆棧引用的指針。
堆棧內存塊,也就是線程棧上保存那個指針指向的地址,它包含三部分,同步快,類型句柄,實例信息。其中類型句柄起到標識該實例所屬類型,所擁有方法表等信息,但現狀是三個實例指向的都是同一個內存地址,也就是它們是一模一樣的。那它們到底是如何識別的?
內存中的表現形式
我再借助SOS來探查具體的信息,稍微改動了下代碼,以便在SOS中更好查看。
static void Main(string[] args) { new Program().Run(); } Employee kinsen; object obj; Person kong; public void Run() { kinsen = new Employee("Kinsen", "Chan"); kinsen.SayHello(); kong = kinsen; kong.SayHello(); obj = kong; Console.WriteLine(kinsen.GetType()); Console.WriteLine(kong.GetType()); Console.WriteLine(obj.GetType()); }
通過!ClrStack命令得到當前對象的地址
000000000023e7e0 000007ff00190163 DebugTest.ClassConvert.Run() PARAMETERS: this = 0x00000000023c5ad0
0:000> !dumpobj 0x00000000023c5ad0 Name: DebugTest.ClassConvert MethodTable: 000007ff00033b68 EEClass: 000007ff00182250 Size: 40(0x28) bytes (E:\Projects\ClassConvert.exe) Fields: MT Field Offset Type VT Attr Value Name 000007ff00033d68 4000001 8 DebugTest.Employee 0 instance 00000000023c5b48 kinsen 000007fef43773f8 4000002 10 System.Object 0 instance 00000000023c5b48 obj 000007ff00033ca0 4000003 18 DebugTest.Person 0 instance 00000000023c5b48 kong
這里能看到DebugTest.ClassConvert類有三個實例,分別是kinsen,obj和kong,它們的value都相同(00000000023c5b48),這里
的確與程序中看到的一模一樣,但是注意,它們的MT,也就是方法表卻不一樣了。再分別把他們解析一下。
//Name=kinsen 0:000> !dumpvc 000007ff00033d68 00000000023c5b48 Name: DebugTest.Employee MethodTable 000007ff00033d68 EEClass: 000007ff00183498 Size: 32(0x20) bytes (E:\Projects\ClassConvert.exe) Fields: MT Field Offset Type VT Attr Value Name 000007fef4377b08 4000004 0 System.String 0 instance 000007ff00033d68 <FirstName>k__BackingField 000007fef4377b08 4000005 8 System.String 0 instance 00000000023c5af8 <LastName>k__BackingField //Name=obj 0:000> !dumpvc 000007fef43773f8 00000000023c5b48 Name: System.Object MethodTable 000007fef43773f8 EEClass: 000007fef3f42200 Size: 24(0x18) bytes (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) Fields: //Name=kong 0:000> !dumpvc 000007ff00033ca0 00000000023c5b48 Name: DebugTest.Person MethodTable 000007ff00033ca0 EEClass: 000007ff001833f0 Size: 32(0x20) bytes (E:\Projects\ClassConvert.exe) Fields: MT Field Offset Type VT Attr Value Name 000007fef4377b08 4000004 0 System.String 0 instance 000007ff00033d68 <FirstName>k__BackingField 000007fef4377b08 4000005 8 System.String 0 instance 00000000023c5af8 <LastName>k__BackingField
或許到這里,的確能解釋為什么即使指向的對象是同一個,但卻能在轉換成其他類型之后做該類型的操作,但是
其中還是如一個黑匣子,我對此依然不明不白。
令人向往的方法表,方法槽表
此外,還有一張圖,
.gif)
這張圖出自微軟,對于一些概念,我還是比較模糊,例如方法表,方法槽表,SOS中的MT應該是方法表呢?還是方法槽表?從圖上看來,方法表的分布比較散,看起來好像沒什么規則,這樣又如何確定方法槽表,方法表與方法槽表之間的關系又是如何呢?很希望大家能踴躍回答,如果有詳細的資料就更好了。
渴望音訊
關于這個類型轉換,類型句柄,方法表的問題糾結折騰了我許久,實在沒辦法了。才大膽發出來,懇求各位前輩,大大能幫小弟解惑。
更多
可能也有童鞋也和我有一樣的疑問,以下是一些我查找的知識點來源
出處:http://kongyiyun.cnblogs.com
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


浙公網安備 33010602011771號