1他们之间有什么差别?struct UserInfoStruct
 2}
前几天在群里聊天,有人问
他们之间有什么差别?string aString = (string)objString;
他们之间有什么差别?
string bString = objString.ToString();
有什么区别,我当时就回答“一个是转型、一个是方法调用”,刚说完就觉得自己是在说废话,其实我也不知道内部到底发生了什么,如是就reflector,ILDASM,google一起上,现在把弄出来的结果整理了一下,share出来,并把相似的几个都集在一起讨论,由于我不懂WinDbg,所以无法深入,就浅尝辄止吧。
下面是main方法的IL代码:
 1他们之间有什么差别?.method private hidebysig static void  Main(string[] args) cil managed
 2他们之间有什么差别?{
 3他们之间有什么差别?  .entrypoint
 4他们之间有什么差别?  // Code size       97 (0x61)
 5他们之间有什么差别?  .maxstack  1
 6他们之间有什么差别?  .locals init ([0object objString,
 7他们之间有什么差别?           [1string aString,
 8他们之间有什么差别?           [2string bString,
 9他们之间有什么差别?           [3string cString,
10他们之间有什么差别?           [4object objInt,
11他们之间有什么差别?           [5int32 aInt,
12他们之间有什么差别?           [6int32 bInt,
13他们之间有什么差别?           [7object objStruct,
14他们之间有什么差别?           [8] valuetype SomeKits.UserInfoStruct aUserInfoStruct,
15他们之间有什么差别?           [9object objClass,
16他们之间有什么差别?           [10class SomeKits.UserInfoClass aUserInfoClass,
17他们之间有什么差别?           [11] valuetype SomeKits.UserInfoStruct CS$0$0000)
18他们之间有什么差别?  IL_0000:  nop
19他们之间有什么差别?  IL_0001:  ldstr      "abc"
20他们之间有什么差别?  IL_0006:  stloc.0
21他们之间有什么差别?  IL_0007:  ldloc.0
22他们之间有什么差别?  IL_0008:  castclass  [mscorlib]System.String
23他们之间有什么差别?  IL_000d:  stloc.1
24他们之间有什么差别?  IL_000e:  ldloc.0
25他们之间有什么差别?  IL_000f:  callvirt   instance string [mscorlib]System.Object::ToString()
26他们之间有什么差别?  IL_0014:  stloc.2
27他们之间有什么差别?  IL_0015:  ldloc.0
28他们之间有什么差别?  IL_0016:  call       string [mscorlib]System.Convert::ToString(object)
29他们之间有什么差别?  IL_001b:  stloc.3
30他们之间有什么差别?  IL_001c:  ldc.i4.5
31他们之间有什么差别?  IL_001d:  box        [mscorlib]System.Int32
32他们之间有什么差别?  IL_0022:  stloc.s    objInt
33他们之间有什么差别?  IL_0024:  ldloc.s    objInt
34他们之间有什么差别?  IL_0026:  unbox.any  [mscorlib]System.Int32
35他们之间有什么差别?  IL_002b:  stloc.s    aInt
36他们之间有什么差别?  IL_002d:  ldloc.s    objInt
37他们之间有什么差别?  IL_002f:  call       int32 [mscorlib]System.Convert::ToInt32(object)
38他们之间有什么差别?  IL_0034:  stloc.s    bInt
39他们之间有什么差别?  IL_0036:  ldloca.s   CS$0$0000
40他们之间有什么差别?  IL_0038:  initobj    SomeKits.UserInfoStruct
41他们之间有什么差别?  IL_003e:  ldloc.s    CS$0$0000
42他们之间有什么差别?  IL_0040:  box        SomeKits.UserInfoStruct
43他们之间有什么差别?  IL_0045:  stloc.s    objStruct
44他们之间有什么差别?  IL_0047:  ldloc.s    objStruct
45他们之间有什么差别?  IL_0049:  unbox.any  SomeKits.UserInfoStruct
46他们之间有什么差别?  IL_004e:  stloc.s    aUserInfoStruct
47他们之间有什么差别?  IL_0050:  newobj     instance void SomeKits.UserInfoClass::.ctor()
48他们之间有什么差别?  IL_0055:  stloc.s    objClass
49他们之间有什么差别?  IL_0057:  ldloc.s    objClass
50他们之间有什么差别?  IL_0059:  castclass  SomeKits.UserInfoClass
51他们之间有什么差别?  IL_005e:  stloc.s    aUserInfoClass
52他们之间有什么差别?  IL_0060:  ret
53他们之间有什么差别?
 

将IL代码和源代码比较得知
string aString = (string)objString;的IL代码是 castclass [mscorlib]System.String
这个过程发生了什么?首先在这个指令之前ldloc.0是将第一个局部变量的引用压入堆栈中,然后从堆栈顶上弹出对象的引用,将这个引用转型为这个指令指定的类型,如果转型成功的话将转型的结果压入栈顶。那什么情况下转型成功,什么情况下转型将不成功呢?当这个栈顶的对象不是期望的类的子类的话那就转型失败了,就会抛出InvalidCastException异常。那如果栈顶的对象是null怎么办?会触发异常么?答案是不会,如果栈顶上的元素是null的时候,转型结果也是null,不会引发什么异常。
对于string bString = objString.ToString()就没有什么好说的了,从生成的代码callvirt   instance string [mscorlib]System.Object::ToString()来看,它调用了object的ToString()方法,使用的是callvirt指令,那实际上调用的是string类里面重写object的那个ToString()。
string cString = Convert.ToString(objString)这种形式在内部到底发生了什么呢?我们看看Convert类的ToString(object)静态方法的实现:

他们之间有什么差别?public static string ToString(object value)

 

Convert.ToString()方法里,首先将对象尝试转型为IConvertible接口,如果转型成功就会调用这个接口的ToString()方法了,所以你想想,如果我们要让我们自己写的类型支持Convert.ToString()这种写法怎么办?那就实现IConvertible接口吧。

object objInt = 5这个又发生了什么?它对应的IL指令是:box [mscorlib]System.Int32,box是装箱指令具体分三步进行

1.在托管堆上分配一块内存,内存的大小是值类型的大小然后加上两个所有引用类型都有的附加字段:SyncBlockIndex和一个放发表指针

2.将栈上的值类型拷贝到刚才申请的类型中

3.返回刚在托管堆上申请的对象引用,将其压入栈

从这里看装箱不仅仅耗费内存还将东西拷贝来拷贝去的,真是赔了夫人又折兵啊。

int aInt = (int)objInt又干了些什么呢?还是类型转换么?它对应的IL代码是

unbox.any [mscorlib]System.Int32

这个称之为拆箱,顾名思义就是将刚才的已装箱类型给“转换”为未装箱时候的值类型,从这个层面看拆箱好像是装箱的“逆过程”,实际上却不是,拆箱是通过这样的两步进行的:

1.从栈上获取托管堆中已装箱对象的地址

2.从已装箱对象中获取刚才那个拷贝过去的值类型的地址

看到没,拆箱比起装箱起来少了一步,这里并没有将已装箱类型中的值类型拷贝到栈上,看起来拆箱并没有涉及到内存的拷贝操作,它做的仅仅是做一下地址的提取,但是实际中拆箱后往往紧跟着的就是内存的拷贝。从上面的代码中我们可以看到装箱和拆箱是很消耗资源的操作,所以我们需要特别注意,特别是一些隐式的,我们常常忽略了。
按照上一小节的结论,string cString = Convert.ToString(objString)能够编译通过是因为int类型实现了IConvertible接口,通过Reflector查看代码果真如此。
上面是对.net基元类型的一些讨论,那么对于自己写的structclass是怎样的呢?
通过IL代码,可知对于值类型的struct
object objStruct = new UserInfoStruct();
UserInfoStruct aUserInfoStruct = (UserInfoStruct)objStruct;
就是装箱拆箱的过程
对于引用类型的class UserInfoClass aUserInfoClass = (UserInfoClass)objClass就是castclass指令的操作。
由于本人对WinDbg一无所知,所以也无法在更深一层次讨论这些机制的最底层实现,实属遗憾,希望能有一些达人对底层做进一步解释。

相关文章: