装箱拆箱

1. 装箱转换

     装箱是隐式的,拆箱是显式的,因为你需要告诉 CLR 你要给拆出来的值赋予什么类型。

装箱与拆箱(又叫取消装箱)就是值类型与引用类型的转换,是值类型和引用类型之间的桥梁。

之所以可以这样转换是因为 CTS 允许这样做。只有值类型才存在装箱和拆箱。通过深入了解装箱与拆箱的过程,我们可以知道其中包含了对堆上内存的操作,故会消耗性能,这是完全不必要的。

另外值得注意的是,装箱需要比原数据更多的空间,因为它需要两个引用类型的标准配置:类型对象指针和同步块索引。

 

    装箱转换是指将一个值类型隐式地转换成一个object 类型,或者把这个值类型转换成一个被该值类型应用的接口类型interface-type。把一个值类型的值装箱,也就是创建一个object 实例并将这个值复制给这个object。比如:


 

      int i = 10;

      object obj = i;


 

用下图所示,可以表示装箱的过程

 值类型=>引用类型

栈=>堆

 

                        装箱的过程

我们也可以用显式的方法来进行装箱操作:


 

    int i = 10;

    object obj =(object)i;


 

2、拆箱转换

    和装箱转换正好相反,拆箱转换是指将一个对象类型显式地转换成一个值类型,或是将一个接口类型显式地转换成一个执行该接口的值类型。

拆箱的过程分为两步:首先,检查这个对象实例,看它是否为给定的值类型的装箱值。然后,把这个实例的值拷贝给值类型的变量。

 利用如下例子,查看一个对象拆箱的过程  。


 

    int i = 10;

    object obj = i;

    int j = (int)obj;


 

    拆箱的过程用图来表示就是:

 

拆箱的过程

 

 

具体过程:

  • 在堆中申请内存,内存大小为值类型的大小,再加上额外固定空间(类型对象指针和同步块索引)。
  • 将值类型的字段值拷贝到新分配的内存中。
  • 返回新引用对象的内存地址(给栈上的引用)。

 

 


我们可以从图中看到,装箱就是生成图中除了一开始 i=1 的变量之外另外两块变量的过程。

 

 

 可以看出拆箱过程正好是装箱过程的逆过程。必须注意,装箱转换和拆箱转换必须遵循类型兼容原则。

尽量避免装箱

     我们之所以研究装箱和拆箱,是因为装箱和拆箱会造成相当大的性能损耗(相比之下,装箱要比拆箱性能损耗大),性能问题主要体现在执行速度和字段复制上。因此我们在编写代码时要尽量避免装箱和拆箱,常用的手段为:

    1. 使用重载方法。为了避免装箱,很多FCL中的方法都提供了很多重载的方法。比如我们之前讨论过的Console.WriteLine方法,提供了多达19个重载方法,目的就是为了减少值类型装箱的次数。比如看下面的这段代码:

      Console.WriteLine(3);

    刚开始你可能觉的3会装箱为string类型,但是实际上这条语句不会进行装箱操作,是因为Console.WriteLine方法有一个重载的方法,参数就是一个int的值。

      public static void WriteLine(int value);

     类似Console.WriteLine方法,还有System.IO.BinaryWriter的Write 方法,System.IO.TextWriter 的Write和WriteLine方法,System.Text.StringBuilder的Append和Insert方法等都提供了大量的重载方 法,以减少装箱次数。所以我们在实际的项目中,应该时刻注意装箱的情况,并且选用合适的重载方法避免装箱。

    2. 使用泛型。因为装箱和拆箱的性能问题,所以在.NET 2.0中引用了泛型,他的主要目的就是避免值类型和引用类型之间的装箱和拆箱。我们常用的集合类都有泛型的版本,比如ArrayList对应着泛型的 List<T>,Hashtable对应着Dictionary<TKey, Tvalue>。

    3. 如果在项目中一个值类型变量需要多次拆装箱,那么可以将这个变量提出来在前面显式装箱。比如下面这段代码:


 

int j = 3;

ArrayList a = new ArrayList();

for (int i = 0; i < 100; i++)

{

    a.Add(j);

}

       // 可以修改为:

int j = 3;

object ob = j;

ArrayList a = new ArrayList();

for (int i = 0; i < 100; i++)

{

    a.Add(ob);

}


 

    4. ToString。这点单独列出来是因为虽然小,但是很实用。虽然表面上看值类型调用ToString方法是要进行装箱的,因为ToString是从基类 继承的方法。但是ToString方法是一个虚方法,值类型一般都重写了这个方法,所以调用ToString方法不会装箱。之前说过String.Format方法容易造成装箱,避免的最佳方法就是在调用这个方法前将所有的值类型参数都调用一次ToString方法。

posted @   wolfsocket  阅读(79)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示