C#装箱和拆箱
发布时间:2020-12-15 17:29:23 所属栏目:百科 来源:网络整理
导读:装箱与拆箱(又叫取消装箱)就是值类型与引用类型的转换,是值类型和引用类型之间的桥梁。 之所以可以这样转换是因为 CTS 允许这样做。只有值类型才存在装箱和拆箱。 装箱是隐式的,拆箱是显式的,因为你需要告诉 CLR 你要给拆出来的值赋予什么类型。 通过深
装箱与拆箱(又叫取消装箱)就是值类型与引用类型的转换,是值类型和引用类型之间的桥梁。 之所以可以这样转换是因为 CTS 允许这样做。只有值类型才存在装箱和拆箱。 装箱是隐式的,拆箱是显式的,因为你需要告诉 CLR 你要给拆出来的值赋予什么类型。 通过深入了解装箱与拆箱的过程,我们可以知道其中包含了对堆上内存的操作,故会消耗性能,这是完全不必要的。 另外值得注意的是,装箱需要比原数据更多的空间,因为它需要两个引用类型的标准配置:类型对象指针和同步块索引。 装箱的过程装箱就是把值类型转换为 object 类型或由此值类型实现的任何接口类型,如下图所示:int i = 1; object o = i;具体过程:
![]() 我们可以从图中看到,装箱就是生成图中除了一开始 i=1 的变量之外另外两块变量的过程。 实际上,仅仅通过观察 C# 代码,是无法意识到装箱的,只有访问对应的 IL 代码才能真正观察到装箱。 IL 代码的装箱指令为 box。上面两行代码对应的 IL 代码为: IL_0040: ldc.i4.1 IL_0041: stloc.2 IL_0042: Idloc.2 IL_0043 : box [mscorlib] System.Int32 IL_0048: stloc.3其中前两行对应 int i=1 这句代码,后三行对应object o=i 这句代码。拆箱的过程简单地说,就是把装箱后的引用类型转换为值类型。由于并非一定成功,所以存在抛出异常的可能。IL 代码的拆箱指令为 unbox。具体过程: int b = (int) o; 1) 检查是否为 Null,否则抛出 NullReferenceException 异常。检查实例是否为给定值类型的装箱值。否则抛出 InvalidCastException 异常,最后获得对象各个成员的地址2) 创建一个新的对象 b,并将第一步获得的值复制到 b 中。 在 CLR via C# 中,拆箱被定义为第一步。下面的代码就是拆箱: (int) o; 但这句代码是无法通过编译的。通常来说,我们拆箱的目的都是为了将值拷贝到一个值类型中,所以拆箱之后,往往伴随着一次值的复制动作。上面的例子中,就将值复制到了变量 b。拆箱对应的 IL 代码为: IL_000a: Idloc.1 IL_000b: unbox.any [mscorlib]System.Int32 IL_0010: stloc.2与拆箱比较,装箱的性能消耗更大,因为引用对象的分配更加复杂,成本也更高,值类型分配在栈上,分配和释放的效率都很高。 装箱过程需要创建一个新的引用类型对象实例。 如何避免拆箱和装箱在 C#1 的时代,没有泛型,我们要定义一组自定义类型的数组只能使用 ArrayList。由于 ArrayList 支持任何类型,所以其方法的参数全都是 object,这意味着即使我们的类型是结构体,也会被隐式地装箱,然后在使用时再拆箱。 C# 2 的泛型解决了这个问题。我们可以通过使用泛型集合避免不必要的装箱和拆箱。 很多地方会出现隐蔽的装箱,例如,对结构体的判等。当我们要实现自定义结构体的判等时: struct Rectangle { public override bool Equals(object obj) { } }我们发现,默认的签名为将两个比较对象转换为 object,这当然会引起装箱了。解决的办法是令结构体实现 IEquatable<T>接口 : struct Rectangle : IEquatable<Rectangle> { public bool Equals(Rectangle r) { //… } }这样一来,即使类型存在两个 Equals,CLR 也会优先选择类型较小的那个,即参数为具体类型的 Equals,而不是参数为 Object 的 Equals。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |