C#中的数据类型包括值类型、引用类型和指针类型,而指针类型只有在不安全代码中使用。值类型包括简单类型、结构和枚举,引用类型包括类、接口、委托、数组和字符串等。为了保证效率,值类型是在栈中分配内存,在声明时初始化才能使用,不能为NULL,而引用类型在堆中分配内存,初始化时默认为NULL。
值类型超出作用范围系统自动释放内存,而引用类型是通过垃圾回收机制进行回收。由于C#中所有的数据类型都是由基类System.Object继承而来的,所以值类型和引用类型的值可以相互转换,而这转换过程也就是所谓的装箱和拆箱。
取消装箱是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:
以下语句同时说明了装箱和取消装箱操作: int i = 123; // a value type
object o = i; // boxing int j = (int)o; // unboxing
下图显示了以上语句的结果。 取消装箱转换 ![]() 要在运行时成功取消装箱值类型,被取消装箱的项必须是对一个对象的引用,该对象是先前通过装箱该值类型的实例创建的。尝试对 null 或对不兼容值类型的引用进行取消装箱操作,将导致InvalidCastException. 示例下面的示例演示无效的取消装箱及引发的 InvalidCastException。使用 try 和catch,在发生错误时显示错误信息。
class TestUnboxing
{
static void Main()
{
int i = 123;
object o = i;
try
{
int j = (short) o;
Console.WriteLine("OK");
}
catch(System.InvalidCastException e)
{
Console.WriteLine("Error");
}
finally //不管如何Finally里的代码都是要执行的。所以是否加Finally 根据情况而定。这是面试里的问题。
{
Console.WriteLine("Over");
}
}
}
输出
Specified cast is not valid. Error: Incorrect unboxing. 如果将下列语句: int j = (short) o; 更改为: int j = (int) o; 将执行转换,并将得到以下输出: Unboxing OK. ----------------------------------------------------------------------------------------------- 为了更深入的理解该知识点 我们从另一个层次面来补充下2011-09-09学习点 .NET 所有的类型都是基于CTS即Common Type System 公共类型系统 基类都为System.Object 然后就是两大类(指针类型除外)值类型和引用类型 值类型包括简单类型(int long shor ....),结构(Struct) 枚举(Enum) 引用类型包括类,委托,接口,数组,字符串........ 为了方便利用 或者说万物即对象万物都是相通的。此时便有了装箱和拆箱操作。 装箱 ----引用类型到值类型。 拆箱-----值类型到引用类型。 还是以一个面试题为例子来说明 int i = 13; 上述代码执行了几次装箱和几次拆箱操作。 可以利用ILDASM来看下: .method private hidebysig static void Main(string[] args) cil managed IL_0016: ldloc.1
(1)前面好说,跟前面一样 object ob = i;引起了一次装箱操作也就是IL_0005处代码。
(2)后面可以看出Console.WriteLine方法调用的是单个String作为参数的版本。因此上面调用了String.Concat方法将i + "," + (Int32)ob这3个值连接产生单个String再传给WriteLine。
(3)String.Concat的重载版本里面找到最匹配的就是Concat(object, object,object)。这样为了匹配这3个参数:
(3.1) IL_000c处代码,第一个参数i被装箱
(3.2)IL_0011 处ldstr "," 就是将字符串','压入堆栈
(3.3)然后 IL_0017 (int32)ob引起了一次拆箱操作
(3.4)我们可怜的(int32)ob,又为了匹配Concat的参数,再次被装箱(IL_001c)
明显后面那个(int32)ob造成了一次不必要的拆箱和装箱操作!所以正因为.net的自动类型处理能力,
还是小心地注意一下写法,否则就会引起不必有的性能损失。
备注看了一段的中间代码 上述有个红色大字号BOX命令 至于这个BOX命令执行了什么呢经网上查找如下:
(1)在堆上分配内存。因为值类型最终有一个对象代表,所有堆上分配的内存量必须是值类型的大小加上容纳此对象及其内部结构(比如虚拟方法表)所需的内存量。
(2)值类型的值被复制到新近分配的内存中
(3)新近分配的对象地址被放到堆栈上,现在它指向一个引用类型
对比下列两个代码段 试着用ILDASM工具看看哪个性能好。 代码段一: int i =13; System.Collection. ArrayList arry1=new System.Collection.ArrayList(); arry1.Add(i); Console.WriteLine("{0}",i);
代码段二: int i = 13; object ob =i; System.Collection. ArrayList arry1=new System.Collection.ArrayList(); array1.Add(ob); Console.WriteLine("{0}",ob);
答案:代码段一执行了2次Box 而二执行了一次Box.
基础点和深入点都讲了 下面我们会提出为何要用到装箱和拆箱呢,有经验的可以根据上面例子进行一个归纳譬如我们用的Console.WriteLine(Pa1,Pa2)一般这的类型都是Object啊 。可以任意找一些String的方法看看。 使用场景1:最普通的场景,调用一个含有类型为object的参数的方法,该object可支持任意类型,以便通用.当你需要将一个值类型传入时,需要装箱. 使用场景2:一个非泛型的容器,同样为了保证通用,而将元素类型定义为object,于是要将值类型数据加入容器是,需要装箱。 |