引言

为什么有装箱和拆箱,两者起到什么作用?NET的所有类型都是由基类System.Object继承过来的,包括最常用的基础类型:int, byte, short,bool等等,就是说所有的事物都是对象。如果程序中所有的类型操作用的是引用类型时,往往导致效率低下,所以.Net通过将数据类型分为值类型和引用类型。

前面文章中讲过;

  • 值类型

定义:值类型是在栈中分配内存,在声明时初始化后才能使用,不能为null。

a、整型:(Sbyte、Byte、Char、Short、Ushort、Int、Uint、Long、Ulong)

b、浮点型:(Float、Double)、decima、bool

c、用户定义的结构(struct)。

  • 引用类型

定义:引用类型是在托管堆中分配内存空间用于存储数据、数据指针、以及Sync等,初始化默认为null。

包括类、接口、委托、数组以及内置引用类型object与string。

存储类型

什么是堆,什么是栈?

1、堆区(heap) 一般由程序员进行申请、释放,若程序员不释放,在程序退出时内存自动释放。

2、栈区(statck)- 由编译器自动分配释放,存放函数的参数值,局部变量的值

3、全局区(静态区)-全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放

4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放 ­

5、程序代码区—存放函数体的二进制代码。

装箱和拆箱定义和过程

通俗上讲,装箱是讲值类型转化为引用类型,

拆箱是将引用类型转化为值类型 。

将值类型与引用类型链接起来为何需要装箱?(为何要将值类型转为引用类型?)

一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。

另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。

  • 装箱操作(boxing):

1、首先从托管堆中为新生成的引用对象分配内存。

2、然后将值类型的数据拷贝到刚刚分配的内存中。

3、返回托管堆中新分配对象的地址。

可以看出,进行一次装箱要进行分配内存和拷贝数据这两项比较影响性能的操作。

  • 拆箱操作(unboxing):

1、首先获取托管堆中属于值类型那部分字段的地址,这一步是严格意义上的拆箱。

2、将引用对象中的值拷贝到位于线程堆栈上的值类型实例中。

经过这2步,可以认为是同boxing是互反操作。严格意义上的拆箱,并不影响性能,但伴随这之后的拷贝数据的操作就会同boxing操作中一样影响性能。

  • 代码实例:

c#代码

bject objValue = 4;
int value = (int)objValue;

上面的两行代码会执行一次装箱操作将整形数字常量4装箱成引用类型object变量objValue;然后又执行一次拆箱操作,将存储到堆上的引用变量objValue存储到局部整形值类型变量value中。

Il代码

locals init (
[0] object objValue,
[1] int32 'value'
) //上面IL声明两个局部变量object类型的objValue和int32类型的value变量
IL_0000: nop
IL_0001: ldc.i4.4 //将整型数字4压入栈
IL_0002: box [mscorlib]System.Int32 //执行IL box指令,在内存堆中申请System.Int32类型需要的堆空间
IL_0007: stloc.0 //弹出堆栈上的变量,将它存储到索引为0的局部变量中
IL_0008: ldloc.0//将索引为0的局部变量(即objValue变量)压入栈
IL_0009: unbox.any [mscorlib]System.Int32 //执行IL 拆箱指令unbox.any 将引用类型object转换成System.Int32类型
IL_000e: stloc.1 //将栈上的数据存储到索引为1的局部变量即value

上述代码中有几个box意味着有几次装箱操作,有几个unbox就是拆箱操作。

在拆箱中注意的问题:

int x = 0; ­
Int32 y = new Int32(); ­
Object o ; ­
o = x; //隐式的装箱。
o = (Int32)y; //显示的装箱。

对于装箱而言,是不存在任何疑问点,既可以用显示(Explicit),也可以用隐式(Implicit)。 ­

x = o; //Error; ­
x = (int)o 或者 x = (Int32)o; //Right; ­

拆箱必须是显示的,而不是隐式的。

Int32 x = 5; ­
Int64 y = 6;
object o;
o = x; or o = (Int32)x;
y = (Int64)o; //It's Error. ­

装箱的类型必须与拆箱的类型一致。而不是什么可隐式转换之类的。所以装箱的时候用的是Int32,拆箱的时候必须是Int32。 ­

避免装箱的方法

通过泛型来避免。

- 非泛型集合

var array = new ArrayList();
array.Add(1);
array.Add(2);
foreach (int value in array)
{
Console.WriteLine(“value is {0}”,value);
}

在向ArrayList中添加int类型元素时会发生装箱,在使用foreach枚举ArrayList中的int类型元素时会发生拆箱操作,将object类型转换成int类型,在执行到Console.WriteLine时,还会执行两次的装箱操作;这一段代码执行了6次的装箱和拆箱操作;

  • 泛型集合
var list = new List<int>();
list.Add(1);
list.Add(2); foreach (int value in list)
{
Console.WriteLine("value is {0}", value);
}

代码和1中的代码的差别在于集合的类型使用了泛型的List,而非ArrayList;我们同样可以通过查看IL代码查看装箱拆箱的情况,上述代码只会在Console.WriteLine()方法时执行2次装箱操作,不需要拆箱操作。

通过重载函数来避免。

Struct A : ICloneable
{
public Int32 x;
public override String ToString() {
return String.Format(”{0}”,x);
}
public object Clone() {
return MemberwiseClone();
}
}
static void main()
{
A a;
a.x = 100;
Console.WriteLine(a.ToString());
Console.WriteLine(a.GetType());
A a2 = (A)a.Clone();
ICloneable c = a2;
Ojbect o = c.Clone();
}

5.0:a.ToString()。编译器发现A重写了ToString方法,会直接调用ToString的指令。因为A是值类型,编译器不会出现多态行为。因此,直接调用,不装箱。(注:ToString是A的基类System.ValueType的方法)

5.1:a.GetType(),GetType是继承于System.ValueType的方法,要调用它,需要一个方法表指针,于是a将被装箱,从而生成方法表指针,调用基类的System.ValueType。(补一句,所有的值类型都是继承于System.ValueType的)。

5.2:a.Clone(),因为A实现了Clone方法,所以无需装箱。

5.3:ICloneable转型:当a2为转为接口类型时,必须装箱,因为接口是一种引用类型。

5.4:c.Clone()。无需装箱,在托管堆中对上一步已装箱的对象进行调用。

有时我们可以提前进行装箱或者拆箱操作

比如:

//当我们如下时:
for (int i = 0; i < arrList.Length; i++)
{
//
}
//我们更因该这样:
int L = arrList.Length;
for (int i = 0; i < L; i++)
{
//
}

附:其实上面的基于一个根本的原理,因为未装箱的值类型没有方法表指针,所以,不能通过值类型来调用其上继承的虚方法。另外,接口类型是一个引用类型。对此,我的理解,该方法表指针类似C++的虚函数表指针,它是用来实现引用对象的多态机制的重要依据

凡事并不能绝对,假设你想改造的代码为第三方程序集,你无法更改,那你只能是装箱了。

c#基础语言编程-装箱和拆箱的更多相关文章

  1. 一、基础篇--1.1Java基础-包装类的装箱和拆箱

    包装类:java是典型的面向对象编程,但是八种基本数据类型并不支持面向对象编程.基本类型的数据不具备对象的特性,没有属性和方法.沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单.有效地进行常规数据 ...

  2. Java基础 【自动装箱和拆箱、面试题】

    JDK 1.5 (以后的版本)的新特性自动装箱和拆箱 1. 自动装箱:把基本类型转换为包装类类型 int a =10; Integer i = new Integer(a); Integer valu ...

  3. C#基础学习之装箱,拆箱

    装箱,拆箱这两个的大条件是有继承关系. 装箱:值类型转换为引用类型 拆箱:引用类型转换为之类 但是要注意大条件. string (引用类型)  int(值类型)   这个转换因为没有继承关系,内存中没 ...

  4. Java基础(十四)--装箱、拆箱详解

    Java中基本数据类型都有相对应的包装类 什么是装箱?什么是拆箱? 在Java SE5之前,Integer是这样初始化的 Integer i = new Integer(10); 而在从Java SE ...

  5. java 基础知识七 装箱和拆箱

    java  基础知识七  装箱和拆箱 数据类型可分为两大种,基本数据类型(值类型)和类类型(引用数据类型) 装箱:把基本类型用他们相对应的引用类型包装起来,使他们可以具有对象的特质    基本数据类型 ...

  6. Java 基础 - 装箱, 拆箱

    总结 1-装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的.(xxx代表对应的基本数据类型).例如:在装箱的时候自动调用的是Integer的 ...

  7. C#基础知识系列二(值类型和引用类型、可空类型、堆和栈、装箱和拆箱)

    前言 之前对几个没什么理解,只是简单的用过可空类型,也是知道怎么用,至于为什么,还真不太清楚,通过整理本文章学到了很多知识,也许对于以后的各种代码优化都有好处. 本文的重点就是:值类型直接存储其值,引 ...

  8. 基础系列(4)—— C#装箱和拆箱

    一 装箱和拆箱的概念 装箱是将值类型转换为引用类型 : 拆箱是将引用类型转换为值类型 : 值类型:包括原类型(Sbyte.Byte.Short.Ushort.Int.Uint.Long.Ulong.C ...

  9. 【.Net基础二】浅谈引用类型、值类型和装箱、拆箱

    目前在看CLR via C#,把总结的记下来,索性就把他写成一个系列吧. 1.[.Net基础一] 类型.对象.线程栈.托管堆运行时的相互关系 2.[.Net基础二]浅谈引用类型.值类型和装箱.拆箱 引 ...

随机推荐

  1. SGU 296.Sasha vs. Kate(贪心)

    题意: 给出长度为n(<=1000)的一个数.输出删掉k个数字后的最大值. Solution: 简单贪心. s[i]代表数字s的第i位. 从前往后第一个满足s[i]>s[i-1]的位置,最 ...

  2. Google HTML/CSS/JS代码风格指南

    JS版本参见:http://www.zhangxinxu.com/wordpress/2012/07/google-html-css-javascript-style-guides/ HTML/CSS ...

  3. 算法专题训练 搜索a-T3 Ni骑士(ni)

    搞了半天八数码弄不出来就只好来打题解  这道题是在搜索a碰到的(链接: http://pan.baidu.com/s/1jG9rQsQ ) 感觉题目最大亮点就是这英文简写"ni", ...

  4. php 微信支付jsapi

    首先你们公司开通微信支付功能后,会收到一份邮件,里面有账户相关信息,一般有:微信支付商户号,商户平台登录帐号,商户平台登录密码,申请对应的公众号,公众号APPID. 1.下载demo:用上面信息登陆& ...

  5. js获取时间加多山天和时间戳转换成日期

    function huoqu(){    var data = $("#data").val();//获取的时间    var day = $('#day').val();//往后 ...

  6. 注册表和ODBC

         注册表使用的是树型体系结构,树中的每个结点称键.每个键也可以包含其他的键或子键.它允许进一步的分支,也即为值,它用来存储有效的数据.在注册表中,注册表用键来组织数据,一个键中的值用它们的名来 ...

  7. js 删除数组方法

    今天遇到一个比较脑残的问题 ,在在用js删除数组的时候 delete 数组[下标]的方法删除数组时,该数组的下标变为null,但是数组的长度并没有发生相应的变化 转而使用splice(小标,第N个)删 ...

  8. objective-C: nonatomic retain copy assgin 等属性详解

    http://my.oschina.net/u/728866/blog/90798 property,可以提供的功能有:提供成员变量的访问方法的声明.控制成员变量的访问权限.控制多线程时成员变量的访问 ...

  9. g++的常用参数

    -c 编译成目标文件.o-o 指定输出文件名,输出文件名跟在-o后面,用空格分隔.如果不使用这个选项,缺省的输出文件名为a.out.-g 产生有调试信息的可执行文件-w 不产生警告信息-l 连接指定的 ...

  10. 详解 Android 的 Activity 组件

    Activity 的生命周期 和 J2ME 的 MIDlet 一样,在 android 中,Activity 的生命周期交给系统统一管理.与 MIDlet 不同的是安装在 android 中的所有的 ...