Chinaunix首页 | 论坛 | 博客
  • 博客访问: 566488
  • 博文数量: 155
  • 博客积分: 4015
  • 博客等级: 上校
  • 技术积分: 1625
  • 用 户 组: 普通用户
  • 注册时间: 2005-11-18 16:55
文章分类

全部博文(155)

文章存档

2009年(20)

2008年(39)

2007年(66)

2006年(29)

2005年(1)

我的朋友

分类:

2007-11-12 19:24:13

  C#中有个术语(term):装箱(Boxing)和拆箱(Unboxing)。这两个动作也常常是低效语句的“帮兄”。在了解Boxing和Unboxing之前先来看看C#中的另一个概念:值类型(value type),引用类型(reference type).
 
 
1. Reference type and value type.
 
  在C#中定义几个变量例如:

int age = 12;
MessageType msgType = MessageType.OK //enum型


string name = "Jerry"
object o = new OleDbConnection();

其中age,msgType是值类型,而name,o属于引用类型。C#中规定:
以下类型是值类型:
  数值型(int,float.decimal...),
  结构体(struct),
  枚举型(enum).
 
而像:
  类类型(class),
  接口(interface),
  委托(delegate),
  object,
  string.
则属于引用类型。
 
 
1st,Difference between the reference type and the value type:
 
  其实value type和reference type的本质差别的内存的分配方式不同。类似于上面的定义,在内存中的方式如下图:
  在定义一个值类型时,会在线程的Stack中为该变量分配空间,并在此空间中保存该变量的值。而在定义一个引用类型时会在Managed heap(托管堆)中为该变量分配足够大的空间,并返回该空间的"引用"(即Managed heap中的地址),并保存在Stack上.任何对Managed heap上对象的访问都必须通过Stack上的"引用/指针"来进行.
  例如上图,在stack中,age或MsgType中保存的是“值”,而name或o中保存的是“引用”,可以通过该引用来访问Heap上的实际值("Jerry"字串或OleDbConnection对象)。
  从图中还可以看出:“引用类型多了一层间接性”.当我们访问name时,首先找到name,由于name是一个reference type所以找到的name只是一个"reference"。真正的值分配在Heap上,并通过该"reference"来访问。
   注:此处"reference" 即是 托管对象(managed object)在托管堆(managed heap)中的地址.之所以使用"reference"是因为C#.Net中好像并不提倡很直白的说"地址"(address),或是"指针"(pointer)...,下同.
 
 
2nd,Something you must notice.
 
  变量(或对象)是在Statck中分配空间,还是在Managed heap中分配中空完全取决于该对象是reference type还是value type.如下面的代码:

int iVal = new int(); //iVal保存一个int型值


string name = "jerry"; //name保存"jerry"在Managed heap上的引用

 
 
2,Boxing and Unboxing.
 
1st, Boxing.
 
   Boxing的动作分为两步:
1,将Stack中的值类型Copy至Heap中;
2, 在Stack上通过一个"reference"来访问位于Heap中的Copy值;
例如:
 

//first copy the value of age(12) to heap.then rAge refer to heap which saved the copyed value of 12.
object rAge = age;

 
  在内存中的表示如下:
 
2nd, Why we need boxing and when.
 
  一些函数只接受object型的参数,其实我个人更愿意称些类函数为泛型函数.例如ArrayList.Add()函数,该函数只接爱object型参数,而当你写出如下代码时:
   int val = 100;
   ArrayList al = new ArrayList();
   al.Add(val);  
此时便发生Boxing,将val的值Copy至Managed heap中,并该返回的"reference"通过al.Add(Object o)方法加入至al中.
 
3rd, Unboxing.
 
   Unboxing的动作也分为两步:
1,通过"reference"引用到位于Managed heap中的值;
2,将该值Copy至值类型的变量中保存(该变量位于Stack上);
   例如下面的代码:

int val = 12;


object o = val
int b = (int)o//Unboxing

在内存中发生的动作如下图:
 
 
4th, Why we need unboxing and when.
 
  简单点讲,想让那些已装箱的数据保存在值类型的变量中时,就需要Unboxing.例如接上面的ArrayList的代码.当我需要将al中的元素保存在一个int型(本质是int型属于值类型)变量中,就需要Unboxing.
  int retVal = al[0];      //Error,不能将引用类型的object保存在值类型int中.
  int iVal = (int)al[0]; //OK,通过Unboxing将元素copy至stack中的iVal中.
 
注意:此时al[0]引用的堆上的变量(对象)和位于Stack中的iVal变量是两个互不相干的变量.
 
 
5th, Something you must notice.
 
  装箱(Boxing)是会做一些类似Copy,创建托管堆上的对象(Create object in managed heap),并有可能需要做垃圾回收(Garbage Collection)等动作.所以会带来一些性能损耗(Overhead).其中创建托管堆上的对象带来的性能消耗占了很大比重.这是因为在托管堆上创建一个对象有以下几个动作来完成:
  1,在堆上分配足够大的空间;
  2,可能带来一次垃圾回收(GC)的动作;
  3,堆上对象都要附加两个成员:类型对象&同步块索引;
  4,将Stack上变量Copy至heap中;
 
  相比之下拆箱(Unboxing)只做一次Copy的动作,而且通常value type都是一些"light variant".所以由拆箱带来的性能损耗会小的多,一般情况下可以忽略.
 
 
 
 
 
 
 
 
 
 
 
Jerry.Chow
     11/12'07
 
 
阅读(832) | 评论(0) | 转发(0) |
0

上一篇:用之有度

下一篇:走路 or 散步

给主人留下些什么吧!~~