装箱、转型、方法调用他们究竟有什么区别?_.Net教程
推荐:使用Ajax后,原来导出功能失败的解决方法问题描述:我们的产品在Ajax后(使用微软的UpdatePanel),其中的导出功能出现错误。因为导出功能使用了Response直接输出内容,而Ajax的异步方式对此不能解析导致出现错误。 解决过程:在网上
以下为引用的内容: 装箱、转型、方法调用这些我们天天进行的日常工作之前到底有什么差别? |
以下为引用的内容: struct UserInfoStruct 2{ 3 public int UserId; 4 public string UserName; 5} 6class UserInfoClass 7{ 8 private int UserId; 9 private string UserName; 10} 11class Program 12{ 13 14 static void Main(string[] args) 15 { 16 object objString = "abc"; 17 18 string aString = (string)objString; 19 string bString = objString.ToString(); 20 string cString = Convert.ToString(objString); 21 22 object objInt = 5; 23 int aInt = (int)objInt; 24 int bInt = Convert.ToInt32(objInt); 25 26 object objStruct = new UserInfoStruct(); 27 UserInfoStruct aUserInfoStruct = (UserInfoStruct)objStruct; 28 29 object objClass = new UserInfoClass(); 30 UserInfoClass aUserInfoClass = (UserInfoClass)objClass; 31 } 32} |
前几天在群里聊天,有人问:
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 ([0] object objString, 7 [1] string aString, 8 [2] string bString, 9 [3] string cString, 10 [4] object objInt, 11 [5] int32 aInt, 12 [6] int32 bInt, 13 [7] object objStruct, 14 [8] valuetype SomeKits.UserInfoStruct aUserInfoStruct, 15 [9] object objClass, 16 [10] class 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) { return ToString(value, null); } public static string ToString(object value, IFormatProvider provider) |
在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基元类型的一些讨论,那么对于自己写的struct和class是怎样的呢?
通过IL代码,可知对于值类型的struct
object objStruct = new UserInfoStruct();
UserInfoStruct aUserInfoStruct = (UserInfoStruct)objStruct;
就是装箱拆箱的过程
对于引用类型的class UserInfoClass aUserInfoClass = (UserInfoClass)objClass就是castclass指令的操作。
由于本人对WinDbg一无所知,所以也无法在更深一层次讨论这些机制的最底层实现,实属遗憾,希望能有一些达人对底层做进一步解释。
分享:ASP.NET蔚昜璃唗蹈趙傖Binary揣湔祫DB or File郔輪衄?婓枒蹦涴跺恀枙..苤萊竭屾俙涴跺陲昹..憩善厙繚奻梑賸珨虳訧埭..諒湮模?睡蔚昜璃唗蹈趙傖Binary Data..?綴揣湔善訧蹋踱麼紫黓孬.. 絞?褫眕唗蹈趙..憩珨隅褫眕毀唗蹈趙賸...?妗唗蹈趙跡宒
- asp.net如何得到GRIDVIEW中某行某列值的方法
- .net SMTP发送Email实例(可带附件)
- js实现广告漂浮效果的小例子
- asp.net Repeater 数据绑定的具体实现
- Asp.Net 无刷新文件上传并显示进度条的实现方法及思路
- Asp.net获取客户端IP常见代码存在的伪造IP问题探讨
- VS2010 水晶报表的使用方法
- ASP.NET中操作SQL数据库(连接字符串的配置及获取)
- asp.net页面传值测试实例代码
- DataGridView - DataGridViewCheckBoxCell的使用介绍
- asp.net中javascript的引用(直接引入和间接引入)
- 三层+存储过程实现分页示例代码
- 相关链接:
- 教程说明:
.Net教程-装箱、转型、方法调用他们究竟有什么区别?。