避免不必要的装箱与拆箱--降低意外的性能损失
先介绍一下什么装箱和拆箱。所谓装箱是指将值类型转换成引用类型的过程,而将引用类型转换为值类型就是拆箱。
装箱的例子:
int aa=3;//值类型
object fx=aa;//aa装箱成了fx
这里aaa是一个值类型,fx是一个引用类型,在 C# 的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接
或间接从 Object 继承的。可以将任何类型的值赋给 object 类型的变量。
接着我们看一下编译器在这个过程中都做了些什么?用ILDASM工具查看下底层代码:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 11 (0xb)
.maxstack 1
.locals init ([0] int32 aaa,[1] object fx)
IL_0000: nop
IL_0001: ldc.i4.3
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: box [mscorlib]System.Int32
IL_0009: stloc.1
IL_000a: ret
} // end of method Program::Main
.locals这里定义了两个局部变量,nop表示用于在填充字节代码时填充空间,ldc.i4.3表示压入指定的int32值,其值是3。stloc.0表示将值弹
出到第一个局部变量中,0代表第一个变量。ldloc.0表示将值压入到第一个局部变量里。
接下来执行box(装箱)将值类型转换为引用类型。然后stloc.1将值再弹入第二变量fx里。
拆箱的例子:
延续上面的例子,现在把装箱后的fa再转换成值类型:
int uu=(int)fx;//拆箱
可以看到,在装箱时并没有使用显示转换,但拆箱时却要使用。这是因为在拆箱时对象可能会被转换成任何类型,所以要明确的指出要转换的
类型,以便编译器能够检验出这种类型转换是否有效。
接下来再看一个例子:
int i=9;
object o=i;
Console.writeLine(i +","+(int32)o);
大家看看,在上面这个过程中到底发生了几次装箱和拆箱的操作?答案是共发生了3个装箱和1个拆箱操作。
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 51 (0x33)
.maxstack 3
.locals init ([0] int32 i,
[1] object o)
IL_0000: nop
IL_0001: ldc.i4.s 9
IL_0003: stloc.0
IL_0004: ldloc.0
IL_0005: box [mscorlib]System.Int32
IL_000a: stloc.1
IL_000b: ldloc.0
IL_000c: box [mscorlib]System.Int32
IL_0011: ldstr ","
IL_0016: ldloc.1
IL_0017: unbox.any [mscorlib]System.Int32
IL_001c: box [mscorlib]System.Int32
IL_0021: call string [mscorlib]System.String::Concat(object,
object,
object)
IL_0026: call void [mscorlib]System.Console::WriteLine(string)
IL_002b: nop
IL_002c: call string [mscorlib]System.Console::ReadLine()
IL_0031: pop
IL_0032: ret
} // end of method Program::Main
通过观察上面的代码,可以看到有3次装箱操作,而第3次装箱操作正是发生在String.Concat方法之前,可以看到这里的string.Concat使用了
三个object参数的重载,所以在Console.writeline(i+","+(int32)O)时,又对O进行了一次隐式装箱。
通过这里例子主要想说明由于.net具有类型自动处理能力,如果代码中隐式的装箱和拆箱操作过多会极大的降低性能,所以在构造代码时应该尽量避免这些无谓的性能损失。
浙公网安备 33010602011771号