.NET Core CSharp 中级篇 2-1 装箱与拆箱


.NET Core CSharp 中级篇 2-1

全文目录

(Github).NET Core Guide

本节内容为装箱与拆箱

简介

装箱和拆箱是一个相对抽象的概念。你可以想象一下一堆满载货物的大卡车,他是由许多工人将货物集中堆放装入的,对于我们而言在没有打开货箱的时候,我们可以知道这是一辆运货的卡车,里面有着许多货物,但是具体货物是什么,我们只有打开后才能知道,并且对于货箱而言,它可以存放任意体积小于自身的货物,也就是说货箱具有通配性。事实上在C#中也是这样,装箱就是将具有实际数据的变量(值类型)打包成一个引用类型(Object),而我们货物到货箱的变化,就是我们本节所需要谈论的装箱与拆箱。利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链接起来。

装箱

装箱是将值类型转换为引用类型,在此前对于基础类型的讲述中,我曾经提到过值类型是在栈中进行分配的,而引用类型是在堆中进行分配,并且需要注意的是,这个堆,是托管堆。托管堆对应于垃圾回收,也就是说用垃圾回收堆中存储值类型。装箱是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。这里的运用一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意类型,因为所有类型都隐式的继承于Object类,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。

这是一个非常简单的装箱操作:

double price = 13.53;
object temp = price;

这段代码看似异常的和谐和简单,但是你是否想过这个过程发生了什么呢?

还记得我们在类的生命周期中讲到的类的创建过程吗?装箱事实上是一样的,装箱对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。

  • 新分配托管堆内存,值得注意的是,这里内存需要加上方法表指针和SyncBlockIndex指针
  • 将值类型的实例字段拷贝到新分配的内存中。
  • 返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。

显然,从装箱的过程上可以看出,装箱时,生成了一个全新的引用类型,创建类型必定伴随着相对较大的时间损耗。所以应该尽量避免装箱。通常对于装箱的情形,我们可以通过重载函数或者通过泛型来避免。但是假设你想改造的代码为第三方程序集,你无法更改,那你只能是装箱了。对于装箱的过程,在C#中都是隐式的,如果你想要观察这个过程,我建议你使用dnSpy或者ILSpy进行反编译分析IL代码。

不过装箱看似只是一个损耗性能的操作,偶尔也是有作用的一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。

并且特别的,对于已装箱的对象,因为无法直接调用其指定方法,所以必须先拆箱,再调用方法,但再次拆箱,会生成新的栈实例,而无法修改装箱对象。这句话我此前学习C#的时候也纠结了一段时间,后来恍然大悟。直白的意思有点类似于你克隆了你自己,和你一模一样,但是你两是同一个人吗?显然不是,你操作克隆人并不会对你有任何的影响。

下面这段代码你可以尝试一下

struct Test
{
    public int x;
    public void test(int x)
    {
        this.x = x;
    }
}


Test t = new Test();
t.x = 100;
object a = t;//装箱
((Test)a).test(300);//x还是100不变,为什么

拆箱

相对于装箱,将一个引用类型(object)类型转换成值类型的过程就是拆箱,说明确一点就是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。拆箱会检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。不过我查阅了很多资料,对于拆箱操作,讲的少之又少,我猜测,拆箱过程中,会调用GetType这种方法进行严格的匹配。

double price = 13.53;
object obj = price;
double temp = (double) obj;

这是一个拆箱的过程,是将值类型转换为引用类型,再由引用类型转换为值类型的过程。首先获取托管堆中属于值类型那部分字段的地址,这一步是严格意义上的拆箱。将引用对象中的值拷贝到位于线程堆栈上的值类型实例中。可以认为和装箱是互反操作。严格意义上的拆箱,并不影响性能,但伴随这之后的拷贝数据的操作就会同装箱操作中一样影响性能。

如果我的文章帮到了你,请为我点一个推荐关注,在Github项目页点一颗star,感谢支持

后续我会补上习题以及图片






作  者:WarrenRyan
出  处:https://www.cnblogs.com/WarrenRyan/
关于作者:热爱数学、热爱机器学习,喜欢弹钢琴的不知名小菜鸡。
版权声明:本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。若需商用,则必须联系作者获得授权。
特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【BiBili——小陈的学习记录
Github——StevenEco
BiBili——记录学习的小陈(计算机考研纪实)
掘金——小陈的学习记录
知乎——小陈的学习记录

联系方式:

电子邮件:cxtionch@live.com

社交媒体联系二维码: