减少装箱与拆箱

简介: 减少装箱与拆箱

在 .NET 中存在一个的冲突,值类型不应该被设计为多态类型,但是 .NET Framework 又必须把 System.Object 设计为引用类型,并把它作为整个对象体系的基础。针对这一冲突 .NET 引入了装箱与拆箱。所谓的装箱就是把值类型放在非类型化的引用对象中,使得需要使用引用类型的地方也可以使用值类型,而拆箱指的是把已经装箱的值复制出来一份。在只能使用 System.Object 类型或接口类型的地方使用值类型,那么就必定设计到装箱和拆箱操作。但是装箱和拆箱操作严重的影响了所开发的应用程序的性能,并且在部分情况下还会创建对象的临时拷贝,进而会造成难以查找的 bug 。下面我们就具体来讲解一下如何减少装箱和拆箱。


零、基本方法需要注意

装箱操作会把值类型转换为引用类型,新创建的引用对象被分配在了堆上面,里面包含了对原值的一个拷贝,而且还实现了值类型的所有接口,当有外部代码查询里面的内容时,系统会将里面的原值拷贝一份返回给调用方。这种拷贝仅仅是一次性的,下次再次查询时会重新拷贝一份里面的原值。在 .NET 2.0 以后我们可以使用泛型类型及其方法来取代大部分装箱与拆箱操作,但是 .NET 中依然存在大量的方法接收 System.Object 类型的参数,因此在以值类型为参数调用这些方法的时候依然会发生装箱和拆箱操作。例如下面这段代码就用到了装箱:

int code=100;
Console.WriteLine($"我的数学成绩是 {code}");

上面的代码看似简单,实际上系统进行了复杂的操作。首先系统会创建 System.Object 引用构成的数组,然后交给编译器生成的方法去解析,同时因为 code 是值类型的变量因此还需要进行装箱操作。另外代码中隐式的调用了 ToString() 方法,这个操作相当于在装箱后的原值上调用。这些操作类似于如下的代码:

int code = 100;
object o = code;
Console.WriteLine($"我的数学成绩是 {o.ToString()}");

针对 Object 数组来创建字符串的方法会产生和下面这段代码类似的逻辑来处理 Object :

object para =100;
object o=para;
int num = (int)o;
string output = num.ToString();

如果要避免上述问题,我们可以提前把值手动转化为 string 类型,也就是显示的调用 ToString 方法,这样就可以防止编译器将其隐式的转换为 System.Object 。

int code=100;
Console.WriteLine($"我的数学成绩是 {code.ToString()}");

避免装箱第一原则:注意代码中会隐式转换成 System.Object 的位置,避免在不需要使用 System.Object 的地方直接使用值类型。

一、泛型方法需要注意

开发人员还可以使用泛型集合来避免拆箱和装箱操作,但是这里需要注意的是 .NET 第一次实现集合所保存的是指向 System.Object 实例的引用,如果在里面放入值类型就会发生装箱操作,从集合中移除时就会发生拆箱操作。下面我们来可一个简单的例子:

public struct Student
{
    public string Name {get;set;}
    public override string ToString()
    {
        return Name;
    }
}
var students = new List<Student>();
Student s = new Student
{
    Name = "张三"
};
students.Add(s);
Student s1 = students[0];
s1.Name="李四";
Console.WriteLine(students[0].ToString());

在上述代码中 Student 是值类型,因此即时编译器会创建一个封闭泛型类型,这样就可以让 Student 对象可以以未装箱的形式放入集合中。但是当我们从旁那个集合中取出一个对象时,取出的是这个对象的一个拷贝,因此当我们修改这个对象的 Name 属性是实际上并不是修改的原来那个对象的 Name 属性。当我们在 students[0] 上调用 ToString 方法时又创建了一份拷贝。因此这里我建议将值类型设计为不可变类型。


二、小结

值类型可以转换为指向 System.Object 或其他接口的引用,因为这种转换是默认发生的,因此产生错误后很难排查。并且把值类型当成多态中的类型还会影响程序的应能,因此需要注意把值类型转换为 System.Object 或其他接口的地方。


目录
相关文章
|
5月前
|
存储 测试技术 索引
在实际编程中避免过度使用自动装箱和拆箱
在实际编程中避免过度使用自动装箱和拆箱
|
缓存 Java 编译器
自动拆箱与装箱
自动拆箱与装箱
|
缓存 Java
包装类(装箱&拆箱&数据类型转换)
​ 在Java5 中添加了两个新特性,那就是自动装箱和拆箱,因为基本类型的广泛使用,但是Java 又是面向对象的语言,所以提供了包装类型的支持。
50 0
|
存储 Java 编译器
Java包装类与自动拆箱装箱
Java包装类与自动拆箱装箱
|
Java 程序员
Java包装类,基本的装箱与拆箱
将原始类型和包装类分开以保持简单。当需要一个适合像面向对象编程的类型时就需要包装类。当希望数据类型变得简单时就使用原始类型。
106 0
|
缓存 算法 Java
【JavaSE基础】包装类全解析以及使用(详解装箱与拆箱)
今天为大家讲解包装类的由来和使用知识,以及它的自动装箱和拆箱。文章将以常用Integer来讲解,同时会讲解Integer的特点。看完这篇保证你收获满满
140 0
【JavaSE基础】包装类全解析以及使用(详解装箱与拆箱)
|
Dubbo Java 编译器
自动装箱、拆箱了解多少?
Java 为我们提供了 8 种基本数据类型,为什么还需要提供各自的包装类型呢?您可能会觉得这个问题问的很奇怪,但是我觉得还是值的思考的。
|
Java 编译器
详解JAVA包装类、自动拆箱和装箱
详解JAVA包装类、自动拆箱和装箱
146 0
详解JAVA包装类、自动拆箱和装箱
|
存储 安全 Java
老生常谈--什么是装箱什么是拆箱
老生常谈--什么是装箱什么是拆箱
300 0