C#装箱和拆箱
2020-02-25 21:47 蜗牛89 阅读(339) 评论(0) 收藏 举报简单的说
装箱:值类型转成引用类型
拆箱:引用类型转成值类型

这里就执行了装箱和拆箱操作。
Box和Unbox的解释参考官网:
说说装箱,步骤如下:
0、将值入栈,(注意,不管这个值是在堆,还是在栈,还是在哪里,都需要先将值入栈)
1、在托管堆中分配内存。分配的内存量是值类型各字段所需的内存量,还要加上类型对象指针和同步块索引所需的内存量
2、把入栈的值,弹出栈,复制到新分配的堆里
3、返回对象地址,把地址入栈
注:两个开销字段——类型对象指针和同步块索引,所需的内存在32位应用程序上需要4+4=8个字节,64位上是16字节
看看例子:object obj = 3;装箱步骤
0、它是先将值3入栈
1、在托管堆中分配内存,在32位应用上是 4(int所需的内存) + 8 = 12个字节
2、把3弹出栈,并复制到新分配的内存堆里
3、把新地址入栈
在申请堆空间的时候,有可能会触发GC。
注意,obj那个3和入栈那个3不是一个东西了。看如下代码的输出:

修改了i,但是obj的值依然是3.
下面看个例子:
int i = 3; object obj = i; Console.WriteLine(i + ", obj: " + (int)obj);
这个代码会进行三次装箱,因为字符串的+用的是String::Concat(object, object, object);

int i = 3; object obj = i; Console.WriteLine(i.ToString() + ", obj: " + obj);
这个代码只会进行一次装箱,即obj = i需要装箱,而i.ToString和obj传给Concat都不会装箱处理。
Obj不进行装箱,因为它本来就是引用类型,这个比较好理解
但i.ToString()这个,涉及的就有点麻烦,参考下图:

此图是《CLR via C#》 (第4版) 周靖 译 的第118页
说说拆箱:
1、获取已装箱对象的各个字段地址
2、把值拷贝到栈上的实例中
拆箱的消耗比装箱的少,但要注意,拆箱前会检查是否是装箱对象,以及会检查引用的对象是不是所需值类型的已装箱实例(就是类型判断,类型不能变,必须要一样)
说说怎么减少装箱拆箱
1、对于集合,尽量使用范型集合,比如:List,Dictionary,尽量不要使用ArrayList和HashTable
2、如果要有字符串连接(concat)啥的,可以手动调用值类型的虚方法ToString()(前提是需要override此方法)
3、如果能预判到后面会重复多次完全一样的装箱操作,可以提前手动装箱,然后后面使用装箱对象,而不是进行多次的装箱
4、使用重载,实现需要支持的所有的值类型参数
5、使用范型
注意:引用类型下的所有内存都是从托管堆(堆)分配,使用引用类型要注意性能问题。
这里引发了两个坑:
1、C#的堆和栈到底有啥区别?
2、CLR的GC机制是啥?
浙公网安备 33010602011771号