C#中的类型转换指的是将一种数据类型转换成另一种数据类型的过程。例如,可以将字符串“123456”转换成整数类型的123456。但并不是所有的类型都可以进行转换(例如不能把DateTime对象转换成int类型),类型之间不能完成转换时将导致编译错误或运行时错误。
类型转换的方式主要有以下几种:
隐式类型转换:由低级别类型向高级别类型的转换过程。例如,派生类可以隐式地转换为它的父类。
显式类型转换:也称为强制类型转换,但是这种转换可能会导致精度损失或者出现运行时异常。
通过is和as运算符可以进行安全的类型转换,通过.NET类库中的Convert类来完成类型转换。
下面主要介绍值类型与引用类型间的一种转换——装箱和拆箱
装箱指的是将值类型转换为引用类型的过程,而拆箱指的是将引用类型转换为值类型。装箱过程中,系统会在托管堆中生成一份栈中值类型对象的副本;而拆箱则是从托管堆中将引用类型所指向的已装箱数据复制回值类型对象的过程。
为了帮助大家更好的理解装箱和拆箱的原理,下面通过示例从内存的角度对两个过程进行深入分析。具体的C#代码如下所示:
int n = 10; object o = n; //装箱,装箱就属于隐式类型转换 int num = (int)o; //拆箱
以上代码分别执行了一次装箱和拆箱操作,装箱操作可以具体分为一下3个步骤:
(1)、内存分配:在托管堆中分配好内存空间以存放复制的实际数据。
(2)、完成实际数据的复制:将值类型实例的实际数据复制到新分配的内存中。
(3)、地址返回:将托管堆中的对象地址返回给引用类型变量。
装箱过程就是通过这3步来完成的,下图更形象地描述了这一过程。
在IL代码中,装箱过程是由box指令来实现的,上一段代码对应的IL代码如下所示:
在这段IL代码中,除了有box指令外,我们还看到了一个unbox指令,正如其字面意思所提示的一样,该指令就是完成拆箱操作的IL指令。拆箱过程也可以化分为具体的3个步骤:
(1)、检查实例:首先检查要进行拆箱操作的引用类型变量是否为null,如果是为null则抛出NullReferenceException异常;如果不为null则继续检查变量是否和拆箱后的类型是同一类型,若结果为否,会导致InvalidCastException异常。
(2)、地址返回:返回已装箱变量的实际数据部分的地址。
(3)、数据的复制:将托管堆中的实际数据复制到栈中。
拆箱的3个步骤可以更形象地理解为下图所示的过程:
对于装箱与拆箱的理解之所以是如此重要,主要是因为装箱和拆箱操作对性能有很大的影响。如果程序代码中存在过多的装箱和拆箱操作,由于两个过程都需要进行数据复制,该操作会耗费大量额外运行时间;并且装箱和拆箱过程中必然会产生多余的对象,这一步加重了GC(垃圾回收器)的负担,导致程序的性能降低,此外还可能引起一些隐藏的Bug。
代码示例:
//ArrayList list = new ArrayList(); //00:00:12.5826633 List<int> list = new List<int>(); //00:00:01.0082705 Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000000; i++) { list.Add(i); } sw.Stop(); Console.WriteLine(sw.Elapsed); Console.ReadKey();
从上代码可以看出其性能比较的差异,所以我们在写代码时,应尽量避免装箱和拆箱操作,最好用泛型来编程。
总结:
装箱就是将 值类型 转换为 引用类型
拆箱就是将 引用类型 转换为 值类型
看两种类型是否发生了装箱或拆箱,要看这两种类型是否存在继承关系。
文章评论