1.理解装箱

简单地说,装箱就是将一个值类型的数据存储在一个引用类型的变量中。

假设你一个方法中创建了一个 int 类型的本地变量,你要将这个值类型表示为一个引用类型,那么就表示你对这个值进行了装箱操作,如下所示:

static void SimpleBox()
{
int myInt = 25;
// 装箱操作
object boxedInt = myInt;
}
 确切地说,装箱的过程就是将一个值类型分配给 Object 类型变量的过程。当你装箱一个值时,CoreCLR 会在堆上分配一个新的对象,并将该值类型的值复制到该对象实例。返回给你的是一个在托管堆中新分配的对象的引用。

2.理解拆箱

反过来,将 Object 引用类型变量的值转换回栈中相应的值类型的过程则称为拆箱。

从语法上讲,拆箱操作看起来就像一个正常的转换操作。然而,其语义是完全不同的。CoreCLR 首先验证接收的数据类型是否等同于被装箱的类型,如果是,它就把值复制回基于栈存储的本地变量中。

例如,如果下面的 boxedInt 的底层类型确实是 int,那就完成了拆箱操作:

static void SimpleBoxUnbox()
{
int myInt = 25;
// 装箱操作
object boxedInt = myInt;
// 拆箱操作
int unboxedInt = (int)boxedInt;
}
记住,与执行典型的类型转换不同,你必须将其拆箱到一个恰当的数据类型中。如果你试图将一块数据拆箱到不正确的数据类型中,将会抛出 InvalidCastException 异常。
为了安全起见,如果你不能保证 Object 类型背后的类型,最好使用 try/catch 逻辑把拆箱操作包起来,尽管这样会有些麻烦。考虑下面的代码,它将抛出一个错误,因为你正试图将装箱的 int 类型拆箱成一个 long 类型
static void SimpleBoxUnbox()
{
int myInt = 25;
// 装箱操作
object boxedInt = myInt;
// 拆箱到错误的数据类型,将触发运行时异常
try
{
long unboxedLong = (long)boxedInt;
}
catch (InvalidCastException ex)
{
Console.WriteLine(ex.Message);
}
}

3.生成的 IL 代码

当 C# 编译器遇到装箱/拆箱语法时,它会生成包含装箱/拆箱操作的 IL 代码。如果你用 ildasm.exe 查看编译的程序集,你会看到装箱和拆箱操作对应的 box unbox 指令:

.method assembly hidebysig static
void '<<Main>$>g__SimpleBoxUnbox|0_0'() cil managed
{
.maxstack 1
.locals init (int32 V_0, object V_1, int32 V_2)
IL_0000: nop
IL_0001: ldc.i4.s 25
IL_0003: stloc.0
IL_0004: ldloc.0
IL_0005: box [System.Runtime]System.Int32
IL_000a: stloc.1
IL_000b: ldloc.1
IL_000c: unbox.any [System.Runtime]System.Int32
IL_0011: stloc.2
IL_0012: ret
} // end of method '<Program>$'::'<<Main>$>g__SimpleBoxUnbox|0_0'

乍一看,装箱/拆箱似乎是一个没啥用的语言特性,学术性大于实用性。毕竟,你很少需要在一个本地 Object 变量中存储一个本地值类型。然而,事实是装箱/解箱过程是相当有用的,因为它允许你假设一切都可以被当作 Object 类型来处理,而 CoreCLR 会自动帮你处理与内存有关的细节。

4.实际应用

让我们来看看装箱/拆箱的实际应用,我们以 C# 的 ArrayList 类为例,用它来保存一批在栈中存储的整型数据。ArrayList 类的相关方法成员列举如下:

public class ArrayList : IList, ICloneable
{
...
public virtual int Add(object? value);
public virtual void Insert(int index, object? value);
public virtual void Remove(object? obj);
public virtual object? this[int index] { get; set; }
}

请注意,上面 ArrayList 的方法都是对 Object 类型数据进行操作。ArrayList 是为操作对象(代表任何类型)而设计的,而对象是在托管堆上分配的数据。请考虑下面代码:

static void WorkWithArrayList()
{
// 当传递给对象的方法时,值类型会自动被装箱
ArrayList myInts = new ArrayList();
myInts.Add(10);
}
尽管你直接将数字数据传入需要 Object 参数的方法中,但运行时自动将分配在栈中的数据装箱。如果你想使用索引器从 ArrayList 中检索一条数据,你必须使用转换操作将堆分配的对象拆箱为栈分配的整型,因为 ArrayList 的索引器返回的是 Object 类型,而不是 int 类型。
static void WorkWithArrayList()
{
// 当传递给需要对象参数的方法时,值类型就自动被装箱
ArrayList myInts = new ArrayList();
myInts.Add(10);
// 当对象被转换回基于栈存储的数据时,就会发生拆箱
int i = (int)myInts[0];
// 由于 WriteLine() 需要的 object 参数,又重新装箱了
Console.WriteLine("Value of your int: {0}", i);
}

在调用 ArrayList.Add() 之前,在栈中分配的 int 数值被装箱了,所以它可以被传入参数为 Object 类型的方法中。从 ArrayList 中检索到 Object 类型的数据时,通过转换操作,它就被拆箱成 int 类型。最后,当它被传递给 Console.WriteLine() 方法时,又被装箱了,因为这个方法的参数是 Object 类型。

5.小结

从程序员的角度来看,装箱和拆箱是很方便的,我们不需要手动去复制和转移内存中的值类型和引用类型的数据。

但装箱和拆箱背后的栈/堆内存转移也带来了性能问题。下面总结一下对一个简单的整型数进行装箱和拆箱所需要的步骤:

在托管堆中分配一个新对象;

在栈中的数据值被转移到该托管堆中的对象上;

当拆箱时,存储在堆中对象上的值被转移回栈中;

堆上未使用的对象将最终被 GC 回收。

尽管很多时候装箱和拆箱操作不会在性能方面造成重大影响,但如果一个像 ArrayList 这样的集合包含成千上万条数据,而你的程序又会频繁操作这些数据,性能的影响还是会很明显的。

所以,我们平时在编程时应当尽量避免发生装箱和拆箱操作。比如对于上面 ArrayList 的示例,如果集合元素类型是一致的,则应当使用泛型的集合类型,比如改用 List、LinkedList 等。

关于C#理解装箱与拆箱的更多相关文章

  1. Java装箱和拆箱的基本概念及使用

    Java装箱和拆箱的基本概念及使用 要理解装箱和拆箱的概念,就要理解Java数据类型 装箱:把基本类型用它们相应的引用类型包装起来,使其具有对象的性质.int包装成Integer.float包装成Fl ...

  2. 深入理解C#的装箱和拆箱(转)

    装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作.  1. 装箱在值类型向引用类型转换时发生 2. 拆箱在引用类型向值类型转换时发生 光上述两句话不难理解,但是往深处了解,就需要一些篇幅来解释了 ...

  3. 全面理解java自动装箱和拆箱(转)

    自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象.自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接. 如 ...

  4. 深入理解C#的装箱和拆箱

    个人理解(本质): 封箱是把值类型转换为引用类型 拆箱是把引用类型转换为值类型 封箱是把值类型转换为System.Object类型,或者转换为由值类型实现的接口类型: 例如: struct Mystr ...

  5. 【深入理解CLR】2:细谈值类型的装箱和拆箱

    装箱 总所周知,值类型是比引用类型更“轻型”的一种类型,因为它们不作为对象在托管堆中分配,不会被垃圾回收,也不通过指针来引用.但在许多情况下,都需要获取对值类型的一个实例的引用.例如,假定要创建一个A ...

  6. Java基础 带你深刻理解自动装箱,拆箱含义

    1.什么是装箱,什么是拆箱装箱:把基本数据类型转换为包装类.拆箱:把包装类转换为基本数据类型.基本数据类型所对应的包装类:int(几个字节4)- Integerbyte(1)- Byteshort(2 ...

  7. 【.Net】深入理解C#的装箱和拆箱

    装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作.  1. 装箱在值类型向引用类型转换时发生 2. 拆箱在引用类型向值类型转换时发生 光上述两句话不难理解,但是往深处了解,就需要一些篇幅来解释了 ...

  8. 深入理解Java中的装箱与拆箱

    一.Java数据类型 1.在说装箱与拆箱之前,先说一下Java的基本数据类型,Java从数据类型上可以划分为值类型与引用类型,值类型是四类八种,分别是: 整数型:byte̵,short̵,int̵,l ...

  9. 深入理解Java之装箱与拆箱

    一.Java数据类型 1.在说装箱与拆箱之前,先说一下Java的基本数据类型,Java从数据类型上可以划分为值类型与引用类型,值类型是四类八种,分别是: 整数型:byte̵,short̵,int̵,l ...

随机推荐

  1. cesium加载gltf模型点击以及列表点击定位弹窗

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 之 ...

  2. vue学习3-VSCode添加自定义代码片段

    1. ctrl+shift+p:打开命令行窗口. 2. 搜索snippets关键字.选择Preferenece:Configure User Snippets 3. 选择html.json,打开这个文 ...

  3. python 元组tuple介绍,使用。

    原文 https://blog.csdn.net/ruanxingzi123/article/details/83184909 一  是什么? # python 元组tuple? ''' 元祖tupl ...

  4. plsql 函数的定义 包规范和包主体。

    /* 一.函数? 1.函数定义 函数的内容 根据实际需要来定义 2.使用的方式 */ -- 定义函数 根据部门编号查询出部门的总人数 create or replace function fn( de ...

  5. JAVA异常与异常处理详解【转】

    感谢!!!原文地址:https://www.cnblogs.com/knightsu/p/7114914.html 一.异常简介 什么是异常? 异常就是有异于常态,和正常情况不一样,有错误出错.在ja ...

  6. Xcode 官方下载地址 https://developer.apple.com/downloads/

    Xcode 官方下载地址 https://developer.apple.com/downloads/

  7. oracle锁表问题处理

    文章转载自:http://blog.itpub.net/31397003/viewspace-2142672/ "ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或 ...

  8. 范数||x||(norm)笔记

    1. 范数的含义和定义 范数是具有"长度"概念的函数.在线性代数.泛函分析及相关领域,是一个函数,它为向量空间内的所有向量赋予非零的正的长度或大小.另一方面,半范数可以为非零的向量 ...

  9. 一加6刷入kali nethunter

    Installing Kali NetHunter On the OnePlus 6 准备工具: adb: https://jingyan.baidu.com/article/22fe7cedf67e ...

  10. 根据经纬度坐标获得省市区县行政区划城市名称,自建数据库 java python php c# .net 均适用

    目录 步骤一.下载省市区边界数据 步骤二.解析CSV文件导入数据库 步骤三.在程序中根据坐标解析获得城市 在LBS应用中,根据坐标来解析获得对应是哪个城市是一个很常见的功能,比如App里面通过手机定位 ...