C# 基础之装拆箱 IL 层
程序员文章站
2024-03-19 09:00:04
...
C# 基础之装拆箱
装箱:当一个值类型转成引用类型,我们可以看成 我们把 1元钱装入钱包中,此时发生装箱操作。
拆箱:当一个引用类型转成值类型,我们可以看成 从钱包中把 1元钱 提取出来。
以上个人通俗白话的理解。
测试案例:
这里我声明了一个引用类型和值类型。
上面是发生装拆箱的代码,很容易看清楚之间的类型转换后的装拆箱,不过我们从底层IL语言来看。
我们先看看装箱操作做了什么事情。
我们可以看的 IL 先入栈,之后发生装箱的操作,之后把计算堆栈的推送给静态字段(赋值)
再看看拆箱操作的IL。
IL 基本一样除了中间的 unbox il指令不一样以外。
那么该如何避免装拆箱?答案:泛型
static int i = 1;
static object i2 = new object();
static void Main(string[] args)
{
Box<int>(i);
BoxRef<object>(i2);
}
//值类型转成引用类型 装箱
static void Box<T1>(T1 it) where T1 : struct
{
T1 ol;
ol = it;
}
static void BoxRef<T1>(T1 it) where T1 : class
{
T1 ol;
ol = it;
}
上述代码拆分值类型与引用类型的泛型(String 特殊)
接着我们看看IL代码
// Token: 0x02000002 RID: 2
.class public auto ansi beforefieldinit ClassLibrary6.Class1
extends [mscorlib]System.Object
{
// Fields
// Token: 0x04000001 RID: 1
.field private static int32 i
// Token: 0x04000002 RID: 2
.field private static object i2
// Methods
// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Header Size: 1 byte
// Code Size: 24 (0x18) bytes
.maxstack 8
/* (16,9)-(16,10) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000251 00 */ IL_0000: nop
/* (17,13)-(17,25) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000252 7E01000004 */ IL_0001: ldsfld int32 ClassLibrary6.Class1::i
/* 0x00000257 280100002B */ IL_0006: call void ClassLibrary6.Class1::Box<int32>(!!0)
/* 0x0000025C 00 */ IL_000B: nop
/* (19,13)-(19,32) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x0000025D 7E02000004 */ IL_000C: ldsfld object ClassLibrary6.Class1::i2
/* 0x00000262 280200002B */ IL_0011: call void ClassLibrary6.Class1::BoxRef<object>(!!0)
/* 0x00000267 00 */ IL_0016: nop
/* (20,9)-(20,10) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000268 2A */ IL_0017: ret
} // end of method Class1::Main
// Token: 0x06000002 RID: 2 RVA: 0x0000206C File Offset: 0x0000026C
.method private hidebysig static
void Box<valuetype .ctor ([mscorlib]System.ValueType) T1> (
!!T1 it
) cil managed
{
// Header Size: 12 bytes
// Code Size: 4 (0x4) bytes
// LocalVarSig Token: 0x11000001 RID: 1
.maxstack 1
.locals init (
[0] !!T1 ol
)
/* (24,9)-(24,10) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000278 00 */ IL_0000: nop
/* (26,13)-(26,21) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000279 02 */ IL_0001: ldarg.0
/* 0x0000027A 0A */ IL_0002: stloc.0
/* (27,9)-(27,10) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x0000027B 2A */ IL_0003: ret
} // end of method Class1::Box
// Token: 0x06000003 RID: 3 RVA: 0x0000207C File Offset: 0x0000027C
.method private hidebysig static
void BoxRef<class T1> (
!!T1 it
) cil managed
{
// Header Size: 12 bytes
// Code Size: 4 (0x4) bytes
// LocalVarSig Token: 0x11000001 RID: 1
.maxstack 1
.locals init (
[0] !!T1 ol
)
/* (30,9)-(30,10) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000288 00 */ IL_0000: nop
/* (32,13)-(32,21) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000289 02 */ IL_0001: ldarg.0
/* 0x0000028A 0A */ IL_0002: stloc.0
/* (33,9)-(33,10) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x0000028B 2A */ IL_0003: ret
} // end of method Class1::BoxRef
// Token: 0x06000004 RID: 4 RVA: 0x0000208C File Offset: 0x0000028C
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Header Size: 1 byte
// Code Size: 8 (0x8) bytes
.maxstack 8
/* 0x0000028D 02 */ IL_0000: ldarg.0
/* 0x0000028E 280F00000A */ IL_0001: call instance void [mscorlib]System.Object::.ctor()
/* 0x00000293 00 */ IL_0006: nop
/* 0x00000294 2A */ IL_0007: ret
} // end of method Class1::.ctor
// Token: 0x06000005 RID: 5 RVA: 0x00002095 File Offset: 0x00000295
.method private hidebysig specialname rtspecialname static
void .cctor () cil managed
{
// Header Size: 1 byte
// Code Size: 17 (0x11) bytes
.maxstack 8
/* (13,9)-(13,26) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x00000296 17 */ IL_0000: ldc.i4.1
/* 0x00000297 8001000004 */ IL_0001: stsfld int32 ClassLibrary6.Class1::i
/* (14,9)-(14,41) C:\Users\admin\source\repos\ClassLibrary6\ClassLibrary6\Class1.cs */
/* 0x0000029C 730F00000A */ IL_0006: newobj instance void [mscorlib]System.Object::.ctor()
/* 0x000002A1 8002000004 */ IL_000B: stsfld object ClassLibrary6.Class1::i2
/* 0x000002A6 2A */ IL_0010: ret
} // end of method Class1::.cctor
} // end of class ClassLibrary6.Class1
没有发生装拆箱,是之间call调用了对于的函数。
我们发现它只是把 0号位置的参数推入计算堆栈并弹出,其他并没什么了。
就有点像C++的内联函数一样,提前生成对应的代码段,通过占位的方式避免装拆箱(不知道这么解释对不对)
总结几点:
1.装拆箱代IL码量少,但是太多性能低下。
2.泛型IL代码量多,但是避免了装拆箱,在目前的PC和移动平台下,基本可以不用考虑(内存换性能)