老话题装箱与拆箱
装箱和取消装箱使值类型能够被视为对象。对值类型装箱将把该值类型打包到 Object 引用类型的一个实例中。这使得值类型可以存储于垃圾回收堆中。取消装箱将从对象中提取值类型。在此示例中,整型变量 i 被“装箱”并赋值给对象 o。
相对于简单的赋值而言,装箱和取消装箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个全新的对象。次之,取消装箱所需的强制转换也需要进行大量的计算。
装箱用于在垃圾回收堆中存储值类型。装箱是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。
此示例通过装箱将整数变量 i 转换为对象 o。这样,存储在变量 i 中的值就从 123 更改为 456。该示例表明原始值类型和装箱的对象使用不同的内存位置,因此能够存储不同的值。
class TestBoxing
{
static void Main()
{
int i = 123;
object o = i; // implicit boxing
i = 456; // change the contents of i
System.Console.WriteLine("The value-type value = {0}", i);
System.Console.WriteLine("The object-type value = {0}", o);
}
}
取消装箱是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:
· 检查对象实例,确保它是给定值类型的一个装箱值。
· 将该值从实例复制到值类型变量中。
上面是MSDN的例子,可以看出它们是很简单的。
装箱是值类型到object类型的转换,它与类型转换有很大的不同,类型转换是由一个类型到其他任何一种类型的转换。如int a=10;byte b =(byte)a; classa c=new classa();object o=c;这些都是类型转换而不是装箱操作,装箱操作有一个要点就是,一定是由值类型向object 类型的转换。
要想真正学会这方面的知识,要知道什么是值类型,有哪几种,在方法调用的时候传入的参数是什么类型的,微软为减少装箱与拆箱操作提供了什么样的便利,在我们编程的时候要注意到哪些问题。下面一一解决:
1、什么是值类型:
值类型主要由两类组成:
· 结构
· 枚举
结构分为以下几类:
· Numeric(数值)类型
· 整型
· 浮点型
· decimal
· bool
· 用户定义的结构。
基于值类型的变量直接包含值。将一个值类型变量赋给另一个值类型变量时,将复制包含的值。这与引用类型变量的赋值不同,引用类型变量的赋值只复制对对象的引用,而不复制对象本身。所有的值类型均隐式派生自System.ValueType。
与引用类型不同,从值类型不可能派生出新的类型。但与引用类型相同的是,结构也可以实现接口。
与引用类型不同,值类型不可能包含 null 值。然而,可空类型功能允许将 null 赋给值类型。
每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。
与引用类型区分开来。
引用类型的变量又称为对象,可存储对实际数据的引用。本节介绍以下用于声明引用类型的关键字:
· class
· interface
· delegate
内置引用类型:
· object
· string
注意:datetime类型跟string很像,不过它是值类型。对datetime变量的修改如:
DateTime t= DateTime.Now;
Console.Write(t.ToLongDateString());
DateTime t2 = t;
t2.AddDays(5);
Console.Write(t2.ToLongDateString());//t2不变
Console.Write(t.ToLongDateString());//t也不会变因为它是值类型的。
Console.Write(t2.AddDays(5));//打印出的日期改变了。但是t2始终是t2
//通过以下方法改变t2
T2=t2.AddDays(5);
2、在方法调用的时候传入的参数是什么类型的
举个例子:
static void Main()
{
Print("geege");//不会发生装箱操作,因为string类型为引用类型。
Print(12);//发生装箱操作因为12为值类型,而Print只接收object类型
int i = 10;
object o = i;//这个很明显是装箱操作
Print(o);//这里不会被装箱了,因为它是引用类型,也不会发生类型转换。
Console.Read();
}
static void Print(object o)
{
Console.WriteLine(o);
}
中间代码可以看出:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// 代码大小 49 (0x31)
.maxstack 1
.locals init ([0] int32 i,
[1] object o)
IL_0000: nop
IL_0001: ldstr "geege"
IL_0006: call void test.Test::Print(object)
IL_000b: nop
IL_000c: ldc.i4.s 12
IL_000e: box [mscorlib]System.Int32
IL_0013: call void test.Test::Print(object)
IL_0018: nop
IL_0019: ldc.i4.s 10
IL_001b: stloc.0
IL_001c: ldloc.0
IL_001d: box [mscorlib]System.Int32
IL_0022: stloc.1
IL_0023: ldloc.1
IL_0024: call void test.Test::Print(object)
IL_0029: nop
IL_002a: call int32 [mscorlib]System.Console::Read()
IL_002f: pop
IL_0030: ret
} // end of method Test::Main
改进方法:
static void Main()
{
Print(12);
int i = 10;
Print(i);//这里调用重载方法Print(int i)没有发生装箱操作,所以就避免了装箱操作的发生。
Console.Read();
}
static void Print(int i)
{
Console.WriteLine(i);// Console.WriteLine能接收int类型所以没有发生装箱操作
}
static void Print(object o)
{
Console.WriteLine(o);
}
3、微软为减少装箱与拆箱操作提供了什么样的便利
首先是MS 为我们提供了丰富的类库和足够多的重载方法来避免装箱操作,其次就是它提供的支持泛型操作。在编程的时候,我们尽量也为我们的设计提供实用的重载方法来避免装箱操作,如果能写出泛型方法就更好了。MS的LINQ就全程提供了泛型操作,不过要写出漂亮的泛型方法还是得好好看一下MSND的。关于泛型在以后会写出来,因为它的设计涉及到很多限制,对这种限制掌握的越全面就越能写出好的泛型方法。
4、在我们编程的时候要注意到哪些问题
在拆箱时要正确的转换类型。
static void Print(Array ilist)
{
foreach (int i in ilist )
{
Console.WriteLine(i);
}
}
static void Print2(Array ilist)
{
foreach (object i in ilist)
{
Console.WriteLine(i);
}
}
上面的例子没有什么意义但说明了一点:
当调用Print(new int[] { 1, 2, 3, 4 });时第一个方法会出现拆箱操作,第二个方法中不会出现这种情况,所以在设计程序时一定要注意这些东西。

浙公网安备 33010602011771号