关于Visual C#装箱与拆箱的研究
在对这个问题展开讨论之前,我们不妨先来问这么几个问题,以系统的了解我们今天要探究的主题。观者也许曾无数次的使用过诸如System.Console类或.NET类库中那些品种繁多的类。那么,我想问的是它们究竟源自何处?C#又是如何联系它们?有没有支持我们个性化扩展的机制或类型系统?又有哪些类型系统可供我们使用呢?如果我们这些PL们连这些问题都不知其然,更不知其所以然的话,C#之门恐怕会把我们拒之门外的。
大多数面向对象的语言都有两种类型:原类型(语言固有的类型,如整数、枚举)和类。虽然在实现模块化和实体化方面,面向对象技术体现了很强的能力,但是也存在一些问题,比如现在提到的这个系统类型问题,历史告诉我们两组类型造成了许多问题。首先就是兼容性问题,这个也是Microsoft使劲抨击的一点,多数的OO语言存在这个弱点,原因就是因为他们的原类型没有共同的基点,于是他们在本质上并不是真正的对象,它们并不是从一个通用基类里派生来的。怪不得,Anders Heijlsberg 笑称其为“魔术类型”。
正是由于这一缺陷,当我们希望指定一个可以接受本语言支持的任何类型的参数的Method时,同样的问题再次袭扰我们的大脑——不兼容。当然,对于C++的PL大拿,也许这个没有什么大不了的,他们会自豪的说,只要用重载的构造器为每一种原类型编写一个Wrapper Class 不就完了嘛!好吧,这样总算是能共存了,但是,接下来我们怎么从这个魔术中得到我们最关心的东东 —— 结果呢?于是,他们依然会自信的打开Boarland,熟练的编写一个重载过的函数来从刚才的那个 Wrapper Class 中获取结果。兄弟 or 姐妹们 ,在当时的历史条件下,你们的行为是创举,但是相对于现在,你将会为此付出代价 —— 效率低下。毕竟,C++更依赖于对象,而非面向对象。承认现实总比死要面子更理智一些!花这么大力气,总算把铺垫说完了,我想说的是:.Net环境的CTS 给我们带来了方便。第一、CTS中的所有东西都是对象;第二、所有的对象都源自一个基类——System.Object类型。这就是所谓的单根层次结构(singly rooted hierarchy)关于System.Object的详细资料请参考微软的技术文档。这里我们简略的谈谈上面提到过的两大类型:Value Type 和 Reference Type。
CTS值类型的一个最大的特点是它们不能为null,言外之意就是值类型的变量总有一个值。在C#中,它包括有原类型、结构、枚举器。这里需要强调一点:在传递值类型的变量时,我们实际传递的是变量的值,而非底层对象的引用,这一点和传递引用类型的变量的情况截然不同;CTS引用类型就好像是类型安全的指针,它可以为null。它包括 如类、接口、委托、数组等类型。对比前面值类型的特点,当我们分配一个引用类型时,系统会在后台的堆栈上分配一个值(内存分配与位置)并返回对这个值的引用;当值为null时,说明没有引用或类型指向某个对象。这就意味着,我们在声明一个引用类型的变量时,易做图作的是此变量的引用(地址),而不是数据。
讨论到这个地方的时候,本篇的主角终于闪亮登场了——欲易做图或者呕吐的同志,请再忍耐一下。我想问一个问题先:在使用这种多类型系统时如何有效的拓展和提高系统的性能?也许就是在黑板上对这个问题的探讨,西雅图的那帮家伙们提出了Box(装箱) and UnBox(拆箱) 的想法。简单的说。装箱就是将值类型(value type)转换为引用类型(reference type)的过程;反之,就是拆箱。(其实这种思想早八辈子就产生了)。下面我们就进一步详细的讨论装箱和拆箱的过程。在讨论中,我们刚刚提到的问题的答案也就迎刃而解了。
首先,我们先来看看装箱过程,为此我们需要先做两个工作:1、编写例程; 2、打开ILDASM(MSIL代码察看工具)为此我们先来看看以下的代码:
using System; namespace StructApp { /// /// BoxAndUnBox 的摘要说明。 /// public class BoxAndUnBox { public BoxAndUnBox() { // // TODO: 在此处添加构造函数逻辑 // } ///////////////////////////////////////////////////////////////////////////////////// static void Main(string[] args) { double dubBox = 77.77; /// 定义一个值形变量 object objBox = dubBox; /// 将变量的值装箱到 一个引用型对象中 Console.WriteLine("The Value is {0} and The Boxed is {1}",dubBox,objBox.ToString()); } ///////////////////////////////////////////////////////////////////////////////////// } } |
代码中,本篇我们只需要关注Main()方法下加注释的两行代码,第一行我们创建了一个double类型的变量(dubBox)。显然按规则,CTS规定double是原类型,所以dubBox自然就是值类型的变量;第二行其实作了三个工作,这个将在下面的MSIL代码中看的一清二楚。第一步取出dubBox的值,第二步将值类型转换引用类型,第三步传值给objBox。
MSIL代码如下:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 40 (0x28) .maxstack 3 .locals init ([0] float64 dubBox, [1] object objBox) IL_0000: ldc.r8 77.769999999999996 IL_0009: stloc.0 IL_000a: ldloc.0 IL_000b: box [mscorlib]System.Double IL_0010: stloc.1 IL_0011: ldstr "The Value is {0} and The Boxed is {1}" IL_0016: ldloc.0 IL_0017: box [mscorlib]System.Double IL_001c: ldloc.1 IL_001d: callvirt instance string [mscorlib]System.Object::ToString() IL_0022: call void [mscorlib]System.Console::WriteLine(string, object, object) IL_0027: ret } // end of method BoxAndUnBox::Main |
在MSIL中,第IL_0000 至 IL_0010 行是描述前面两行代码的。参照C#的MSIL手册,观者不难理解这段底层代码的执行过程,在这我着重描述一下当dubBox被装箱时所发生的故事:(1)划分堆栈内存,在堆栈上分配的内存 = dubBox的大小 + objBox及其结构所占用的空间;(2)dubBox的值(77.7699999999996)被复制到新近分配的堆栈中;(3)将分配给objBox的地址压栈,此时它指向一个object类型,即引用类型。
拆箱作为装箱的逆过程,看上去好像很简单,其实里面多了很多值的思考的东西。首先,box的时候,我们不需要显式的类型转换,但是在unbox时就必须进行类型转换。这是因为引用类型的对象可以被转换为任何类型。(当然,这也是电脑和人脑一个差别的体现)类型转换不容回避的将会受到来自CTS管理中心的监控——其标准自然是依据规则。(其内容的容量足以专门设一章来讨论)好了,我们还是先来看看下面这段代码吧:
using System; namespace StructApp { /// /// BoxAndUnBox 的摘要说明。 /// public class BoxAndUnBox { public BoxAndUnBox() { // // TODO: 在此处添加构造函数逻辑 // } ///////////////////////////////////////////////////////////////////////////////////// static void Main(string[] args) { double dubBox = 77.77; object objBox = dubBox; double dubUnBox = (double)objBox; /// 将引用型对象拆箱 ,并返回值 Console.WriteLine("The Value is {0} and The UnBoxed is {1}",dubBox,dubUnBox); } ///////////////////////////////////////////////////////////////////////////////////// } } |
与前面装箱的代码相比,本段代码多加了一行double dubUnBox = (double)objBox;新加的这行代码作了四个工作,这个也将体现在MSIL代码中。第一步将一个值压入堆栈;第二步将引用类型转换为值类型;第三步间接将值压栈;第四步传值给dubUnBox。
MSIL代码如下:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 48 (0x30) .maxstack 3 .locals init ([0] float64 dubBox, [1] object objBox, [2] float64 dubUnBox) IL_0000: ldc.r8 77.769999999999996 IL_0009: stloc.0 IL_000a: ldloc.0 IL_000b: box [mscorlib]System.Double IL_0010: stloc.1 IL_0011: ldloc.1 IL_0012: unbox [mscorlib]System.Double IL_0017: ldind.r8 IL_0018: stloc.2 IL_0019: ldstr "The Value is {0} and The UnBoxe 补充:软件开发 , C# ,
访问www.zzzyk.com 试试 CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络, |