.NET Core CSharp 中级篇 2-1

本节内容为装箱与拆箱

简介

装箱和拆箱是一个相对抽象的概念。你可以想象一下一堆满载货物的大卡车,他是由许多工人将货物集中堆放装入的,对于我们而言在没有打开货箱的时候,我们可以知道这是一辆运货的卡车,里面有着许多货物,但是具体货物是什么,我们只有打开后才能知道,并且对于货箱而言,它可以存放任意体积小于自身的货物,也就是说货箱具有通配性。事实上在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,感谢支持

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

Github

BiliBili主页

WarrenRyan's Blog

博客园

.NET Core CSharp 中级篇 2-1 装箱与拆箱的更多相关文章

  1. .NET Core CSharp 中级篇 2-2 List,ArrayList和Dictionary

    .NET Core CSharp 中级篇 2-2 本节内容为List,ArrayList,和Dictionary 简介 在此前的文章中我们学习了数组的使用,但是数组有一个很大的问题就是存储空间不足,我 ...

  2. .NET Core CSharp 中级篇2-8 特性标签

    .NET Core CSharp 中级篇2-8 本节内容为特性标签 简介 标签Attribute是一个非常重要的技术,你可以使用Attribute技术优化精简你的代码.特性标签可以运用在程序集,模块, ...

  3. C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析

    前景提要: 编写程序时,也许你不经意间,就不知不觉的使程序代码,发生了装箱和拆箱,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候,减少装箱和拆 ...

  4. .NET Core C# 中级篇2-7 文件操作

    .NET Core CSharp 中级篇2-7 本节内容为文件操作 简介 文件操作在我们C#里还是比较常见的,例如我们读取Excel.Txt文件的内容,在程序中,这些文件都是以流的方式读取进入我们内存 ...

  5. NET Core CSharp初级篇 1-3面向对象

    .NET Core CSharp初级篇 1-3 本节内容为面向对象初级教程 类 简介 面向对象是整个C#中最核心最有特色的一个模块了,它很好的诠释了程序与现实世界的联系. 面向对象的三大特征:继承.多 ...

  6. .NET Core CSharp初级篇 1-8泛型、逆变与协变

    .NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...

  7. .NET Core CSharp初级篇 1-1

    .NET Core CSharp初级篇 1-1 本节内容是对于C#基础类型的存储方式以及C#基础类型的理论介绍 基础数据类型介绍 例如以下这句话:"张三是一名程序员,今年15岁重50.3kg ...

  8. .NET Core CSharp初级篇 1-5 接口、枚举、抽象

    .NET Core CSharp初级篇 1-5 本节内容类的接口.枚举.抽象 简介 问题 如果你需要表示星期或者是某些状态,使用字符串或者数字是否不直观? 你是否发现,无论何种电脑,它的USB口的设计 ...

  9. .NET Core CSharp初级篇 1-6 类的多态与继承

    .NET Core CSharp初级篇 1-6 本节内容为类的多态与继承 简介 终于讲到了面向对象三大特性中的两大特性--继承与多态.通过继承与多态,我们能很好的将类的拓展性发挥到了极致.在下面的内容 ...

随机推荐

  1. Java NIO 学习笔记(三)----Selector

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  2. apache出现forbidden

    <Directory /> Options FollowSymLinks AllowOverride All Order deny,allow allow from all Require ...

  3. yii中 columnszii.widgets.grid.CGridView

    <?php $this->widget('zii.widgets.grid.CGridView', array( 'id'=>'chapter-grid', 'dataProvide ...

  4. Spring Bean 生命周期之“我从哪里来?” 懂得这个很重要

    Spring bean 的生命周期很容易理解.实例化 bean 时,可能需要执行一些初始化以使其进入可用 (Ready for Use)状态.类似地,当不再需要 bean 并将其从容器中移除时,可能需 ...

  5. RocketMQ(7)---RocketMQ顺序消费

    RocketMQ顺序消费 如果要保证顺序消费,那么他的核心点就是:生产者有序存储.消费者有序消费. 一.概念 1.什么是无序消息 无序消息 无序消息也指普通的消息,Producer 只管发送消息,Co ...

  6. 浅入深出Vue:子组件与数据传递

    上一篇了解了组件的概念及在使用中需要注意的地方.在面对单个组件逻辑复杂需要拆分时,难免会遇到父子组件之间数据传递的问题.那么我们来了解一下在父子组件之间进行数据传递时需要遵循哪些约定,以及要注意哪些问 ...

  7. springboot2.x纯注解整合dubbo

    springboot1.x和springboot2.x整合差距挺大的,基于最新的2.x进行整合,使用纯注解的方式 依赖选取 首先pom文件的依赖引入,maven仓库有Apache和alibaba两个 ...

  8. Linux命令学习-ls命令

    Linux中,ls命令的全称是list,主要作用是列出当前目录下的清单. 列出Linux根目录下的所有目录 ls / 列出当前目录下所有文件夹和文件 ls 列出当前目录下所有文件夹和文件(包括以&qu ...

  9. scala刷LeetCode--26 删除排序数组中的重复项

    一.题目描述 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完 ...

  10. Bzoj 1079 着色方案 题解

    1079: [SCOI2008]着色方案 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2237  Solved: 1361[Submit][Stat ...