生活中的装箱与拆箱
我们习惯了在网上购物,这次你想买本编程书 -- 《C 语言从入门到放弃》 ,下单成功后,卖家会帮你将这本入坑指南打好包装,我们可以称之为装箱;经过快递员的快马加鞭,风雨无阻,包裹就直接送到你手上了。你一定会以迅雷不及掩耳盗铃儿响叮当之势拆开包装,这个过程我们可以称之为拆箱,这时,入坑指南就顺利的送到你手上。
C# 的装箱与拆箱
装箱:将值类型(如 int ,或自定义的值类型等)转换成 object 或者接口类型的一个过程。当 CLR 对值类型进行装箱时,会将该值包装为 System.Object 类型,再将包装后的对象存储在堆上。 拆箱就是从对象中提取对应的值类型的一个过程。
装箱是隐式的;拆箱必定是显式的。
相对于简单的赋值而言,装箱和拆箱都需要进行大量的数据计算。对值类型进行装箱时,CLR 必须重新分配一个新的对象。拆箱所需的强制转换也需要进行大量的计算,只是相比,程度不高,并且也可能会出现类型转换的异常情形。如果你的操作正处于循环的中心,通过测试(如:Stopwatch),你会很明显的感觉到性能问题。
.NET 2.0 引入的泛型其实在很大的程度上解决了装拆箱产生的类型转换问题,也减少了类型转换所引起的运行时的异常,从而提高了性能。
static void Main(string[] args) { var i = 123; //System.Int32 obj = i; //对 obj 进行拆箱(显式) i = (int)obj; Console.Read(); }
在这里,我先将变量 i (int 类型)进行了装箱,并分配给对象 obj。其次,再次将对象 obj 进行拆箱(即强转)并重新给变量 i(int 类型)赋值。
直接通过反编译得到的 IL 代码,从 box 和 unbox 这两个指令也可以看出具体在哪一步发生装箱和拆箱操作。
值类型和引用类型
值类型和引用类型,这两者本来没有多大的联系(可能就是基类为 object),设计人员通过一种名为装拆箱的操作使得这两种类型创建了新的联系,让任何值类型都可以当成对象(引用)类型来进行操作。
装拆箱其实就是值类型和引用类型两者之间的类型转换操作。这里,我简单梳理一下这两种类型:
(1)值类型:整型:Int;长整型:long;浮点型:float;字符型:char;布尔型:bool;枚举:enum;结构:struct;它们统一继承 System.ValueType。
(2)引用类型:数组,用户定义的类、接口、委托,object,字符串等。
(3)简单的堆栈图:
图:@ 表示一个引用地址
装箱
装箱就是值类型到 object 类型或者到该值类型所实现的接口类型所实现的一个隐式转换过程(可显式)。装箱的时候会在堆中自动创建一个对象实例,然后将该值复制到新对象内。
var i = 123; //System.Int32 o = i;
从图可知,对象 o 存的是地址引用,指向的是堆上的值,这个值的类型和变量 i 一样,也是 int 类型,值(123)也就是从变量 i Copy 过来的一个副本值而已。
【备注】装箱默认是隐式的,当然,你可以选择显式,但这并不是必须的。
拆箱
拆箱是从 object 类型到值类型,或从接口类型到实现该接口的值类型的显式转换的一个过程。
拆箱:检查对象实例,确保它是给定值类型的一个装箱值后,再将该值从实例复制到值类型变量中。
o = i; j = (int)o; // 拆箱
要在运行时成功拆箱值类型,被拆箱的项必须是对一个对象的引用,该对象是先前通过装箱该值类型的实例创建的。
拆箱时需要注意,转换出现异常的情形:
虽然,decimal 类型可以直接强转为 int 类型,但从调式的结果来看,拆箱时是会引发“转换无效”的异常。要记住,拆箱时强转的值类型,应以装箱时的值类型一致。
C# 基础回顾系列
《C# 知识回顾 - 序列化》
《C# 知识回顾 - 表达式树 Expression Trees》
《C# 知识回顾 - 特性 Attribute》、《剖析 AssemblyInfo.cs - 了解常用的特性 Attribute》
《C# 知识回顾 - 委托 delegate》、《C# 知识回顾 - 委托 delegate (续)》
《C# 知识回顾 - 事件入门》、《C# 知识回顾 - Event 事件》
《string 与 String,大 S 与小 S 之间没有什么不可言说的秘密》
《C# 知识回顾 - 你真的懂异常(Exception)吗?》
《了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数》
《C# 基础回顾 - 匿名方法》
《回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性》
「如果不想在世界上虚度一生,那就要学习一辈子。」-- 高尔基
【博主】反骨仔
【原文】