首先,我们先来看看装箱过程,为此我们需要先做两个工作:1、编写例程; 2、打开ILDASM(MSIL代码察看工具)为此我们先来看看以下的代码:
代码中,本篇我们只需要关注Main()方法下加注释的两行代码,第一行我们创建了一个double类型的变量(dubBox)。显然按规则,CTS规定double是原类型,所以dubBox自然就是值类型的变量;第二行其实作了三个工作,这个将在下面的MSIL代码中看的一清二楚。第一步取出dubBox的值,第二步将值类型转换引用类型,第三步传值给objBox。
MSIL代码如下:
在MSIL中,第IL_0000 至 IL_0010 行是描述前面两行代码的。参照C#的MSIL手册,观者不难理解这段底层代码的执行过程,在这我着重描述一下当dubBox被装箱时所发生的故事:(1)划分堆栈内存,在堆栈上分配的内存 = dubBox的大小 + objBox及其结构所占用的空间;(2)dubBox的值(77.7699999999996)被复制到新近分配的堆栈中;(3)将分配给objBox的地址压栈,此时它指向一个object类型,即引用类型。
拆箱作为装箱的逆过程,看上去好像很简单,其实里面多了很多值的思考的东西。首先,box的时候,我们不需要显式的类型转换,但是在unbox时就必须进行类型转换。这是因为引用类型的对象可以被转换为任何类型。(当然,这也是电脑和人脑一个差别的体现)类型转换不容回避的将会受到来自CTS管理中心的监控——其标准自然是依据规则。(其内容的容量足以专门设一章来讨论)好了,我们还是先来看看下面这段代码吧:
与前面装箱的代码相比,本段代码多加了一行double dubUnBox = (double)objBox;新加的这行代码作了四个工作,这个也将体现在MSIL代码中。第一步将一个值压入堆栈;第二步将引用类型转换为值类型;第三步间接将值压栈;第四步传值给dubUnBox。
MSIL代码如下:
代码中,本篇我们只需要关注Main()方法下加注释的两行代码,第一行我们创建了一个double类型的变量(dubBox)。显然按规则,CTS规定double是原类型,所以dubBox自然就是值类型的变量;第二行其实作了三个工作,这个将在下面的MSIL代码中看的一清二楚。第一步取出dubBox的值,第二步将值类型转换引用类型,第三步传值给objBox。
MSIL代码如下:
在MSIL中,第IL_0000 至 IL_0010 行是描述前面两行代码的。参照C#的MSIL手册,观者不难理解这段底层代码的执行过程,在这我着重描述一下当dubBox被装箱时所发生的故事:(1)划分堆栈内存,在堆栈上分配的内存 = dubBox的大小 + objBox及其结构所占用的空间;(2)dubBox的值(77.7699999999996)被复制到新近分配的堆栈中;(3)将分配给objBox的地址压栈,此时它指向一个object类型,即引用类型。
拆箱作为装箱的逆过程,看上去好像很简单,其实里面多了很多值的思考的东西。首先,box的时候,我们不需要显式的类型转换,但是在unbox时就必须进行类型转换。这是因为引用类型的对象可以被转换为任何类型。(当然,这也是电脑和人脑一个差别的体现)类型转换不容回避的将会受到来自CTS管理中心的监控——其标准自然是依据规则。(其内容的容量足以专门设一章来讨论)好了,我们还是先来看看下面这段代码吧:
与前面装箱的代码相比,本段代码多加了一行double dubUnBox = (double)objBox;新加的这行代码作了四个工作,这个也将体现在MSIL代码中。第一步将一个值压入堆栈;第二步将引用类型转换为值类型;第三步间接将值压栈;第四步传值给dubUnBox。
MSIL代码如下:
在MSIL中,第IL_0011 至 IL_0018 行是描述新行代码的。参照C#的MSIL手册,观者不难理解这段底层代码的执行过程,在此我着重描述一下objBox在拆箱时的遭遇:(1)环境须先判断堆栈上指向合法对象的地址,以及在对此对象向指定的类型进行转换时是否合法,如果不合法,就抛出异常;(2)当判断类型转换正确,就返回一个指向对象内的值的指针。
看来,装箱和拆箱也不过如此,费了半天劲,刚把‘值’给装到‘箱’里去了,有费了更多的劲把它拆解了,郁闷啊!细心的观者,可能还能结合代码和MSIL看出,怎么在调用Console.WriteLine()的过程中又出现了两次box,是的,我本想偷懒逃过这节,但是既然已被发现,就应该大胆的面对,其实这就是传说中的“暗箱操作”啊! 因为Console.WriteLine方法有许多的重载版本,此处的版本是以两个String对象为参数,而具有object 类型的参数的重载是编译器找到的最接近的版本,所以,编译器为了求得与这个方法的原型一致,就必须对值类型的dubBox和dubUnBox分别进行装箱(转换成引用类型)。
所以,为了避免由于无谓的隐式装箱所造成的性能损失,在执行这些多类型重载方法之前,最好先对值进行装箱。现在我们把上述地代码改进为:
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 UnBoxed is {1}" IL_001e: ldloc.0 IL_001f: box [mscorlib]System.Double IL_0024: ldloc.2 IL_0025: box [mscorlib]System.Double IL_002a: call void [mscorlib]System.Console::WriteLine(string, object, object) IL_002f: ret } // end of method BoxAndUnBox::Main |
在MSIL中,第IL_0011 至 IL_0018 行是描述新行代码的。参照C#的MSIL手册,观者不难理解这段底层代码的执行过程,在此我着重描述一下objBox在拆箱时的遭遇:(1)环境须先判断堆栈上指向合法对象的地址,以及在对此对象向指定的类型进行转换时是否合法,如果不合法,就抛出异常;(2)当判断类型转换正确,就返回一个指向对象内的值的指针。
首先,我们先来看看装箱过程,为此我们需要先做两个工作: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 UnBoxed is {1}" IL_001e: ldloc.0 IL_001f: box [mscorlib]System.Double IL_0024: ldloc.2 IL_0025: box [mscorlib]System.Double IL_002a: call void [mscorlib]System.Console::WriteLine(string, object, object) IL_002f: ret } // end of method BoxAndUnBox::Main |
在MSIL中,第IL_0011 至 IL_0018 行是描述新行代码的。参照C#的MSIL手册,观者不难理解这段底层代码的执行过程,在此我着重描述一下objBox在拆箱时的遭遇:(1)环境须先判断堆栈上指向合法对象的地址,以及在对此对象向指定的类型进行转换时是否合法,如果不合法,就抛出异常;(2)当判断类型转换正确,就返回一个指向对象内的值的指针。
看来,装箱和拆箱也不过如此,费了半天劲,刚把‘值’给装到‘箱’里去了,有费了更多的劲把它拆解了,郁闷啊!细心的观者,可能还能结合代码和MSIL看出,怎么在调用Console.WriteLine()的过程中又出现了两次box,是的,我本想偷懒逃过这节,但是既然已被发现,就应该大胆的面对,其实这就是传说中的“暗箱操作”啊! 因为Console.WriteLine方法有许多的重载版本,此处的版本是以两个String对象为参数,而具有object 类型的参数的重载是编译器找到的最接近的版本,所以,编译器为了求得与这个方法的原型一致,就必须对值类型的dubBox和dubUnBox分别进行装箱(转换成引用类型)。
所以,为了避免由于无谓的隐式装箱所造成的性能损失,在执行这些多类型重载方法之前,最好先对值进行装箱。现在我们把上述地代码改进为:
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; object objUnBox = dubUnBox; Console.WriteLine("The Value is '{0}' and The UnBoxed is {1}",objBox,objUnBox); } /// } } |
MSIL代码:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 45 (0x2d) .maxstack 3 .locals init ([0] float64 dubBox, [1] object objBox, [2] float64 dubUnBox, [3] object objUnBox) 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: ldloc.2 IL_001a: box [mscorlib]System.Double IL_001f: stloc.3 IL_0020: ldstr "The Value is '{0}' and The UnBoxed is {1}" IL_0025: ldloc.1 IL_0026: ldloc.3 IL_0027: call void [mscorlib]System.Console::WriteLine(string, object, object) IL_002c: ret } // end of method BoxAndUnBox::Main |
我晕!这算嘛事儿呀!看完后是不是该吐血的吐血,该上吊的上吊呀!相信能坚持到看完最后一个 "!" 的同志一定是个好同志。
其实,我们也可以妄加揣测一下:引用型应当属于高级类型,而值型属于原始类型,箱只是一个概念、一个秩序、一套规则或准确说是一个逻辑。原始的东西作为基础,其复杂性和逻辑性不会很高,而高级的东西就不那么稳定了,它会不断的进化和发展,因为这个逻辑的‘箱’会不断地被要求扩充和完善。由此思路推演,我们就不难预测出未来我们需要努力的方向和成功机会可能存在的地方—— !