Effective C# Item17: Minimize Boxing and Unboxing
在.Net中有值和引用两种类型,它们是不一致的。.Net Framework使用装箱和拆箱来做为二者之间的桥梁。通过装箱可以将一个值类型封装到一个无类型的引用型对象中。拆箱则是将引用类型从包装中释放出来。当我们需要将值类型当作引用类型一样来处理时,装箱和拆箱是必要的。但是这样的操作会浪费额外的资源,一般来说装箱和拆箱时也会创建临时的对象拷贝,这可能会为我们的程序造成难以查觉的隐患。我们应当尽量减少装箱和拆箱操作。
装箱可以将一个值类型转变为引用类型,是分配在堆上的。在这个box中包含了被装箱的值类型的拷贝和其实现的一些接口。当我们需要再将其复原的时候,就创建一个对应的值类型,并赋以箱中的拷贝。
装箱和拆箱是自动发生的,这正是它们危险的地方。当我们将一个值类型当作引用类型来使用的时候,编译器就会自动进行这些转换。另外,当我们通过接口操作值类型的时候也会引发装箱和拆箱。它发生时没有任何警告。下面这个例子中就发生了装箱:
重载的Console.WriteLine的参数是一组引用,对于int类型来说,必须首先进行装箱才能被WriteLine方法使用。另外WriteLine会调用装箱对象的ToString()方法。这样我们的代码其实是比较类似于这样:
首先是装箱:
在拆箱时:
我们不应当让编译器自动做这样的转换。编译器的本意是好的,它自动生成了必要的装箱和拆箱代码来帮助我们通过编译。为了避免发生这种情况,我们可以在WriteLine之前先将我们的类型转变为string:
这样做就可以避免转换的发生。这样一个简单的例子告诉我们应当时刻留意这种转换。如果可以的话,我们应当尽量避免自动转换的发生。
另一个我们可能会遇到的将值类型自动转换为引用类型的地方是.Net 1.x中的集合。在.Net 1.x中集合存储的是System.Object实例。当我们向其中添加值类型的时候,boixng就会发生。当我们使用这些存储的实例时,返回的是被装箱对象中的值类型的拷贝,而非是值类型本身。这容易造成一些潜在的bug:
修改是不会成功的。Person是一个值类型,在储存在ArrayList之前发生了装箱操作。当我们后来在使用它时,返回的是一个拷贝。我们不能通过修改它来达到修改集合中对象的目的。
从这一点也可以看出我们创建值类型时应当将其声明为不可变的。如果我们确实需要可变的值类型,那么我们可以使用System.Array来达到目的。
如果数组不是一个合适的集合,那么我们可以使用接口的方法达到目的。通过接口,我们可以修改box中的值类型的值:
通过接口我们访问到了原始的对象,而不是原对象的拷贝。这样我们就可以修改集合中的值类型的值。
在C# 2.0中上面的许多限制都得到了改进,但是尽管如果次我们还是要尽量避免的。频繁的装箱和拆箱不仅浪费系统资源,降低程序的执行效率,还可能造成意料之外的bug。在可能的情况下尽量不要使用。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
PS:在MSDN上有一篇关于装箱和拆箱不错的文章,介绍了使用装箱类来达到减少这种转换的目的
http://www.microsoft.com/china/msdn/Archives/voices/csharp03152001.asp
回到目录