C#与.NET程序员面试宝典 3.1.3 面试题22:如何解决装箱和拆箱引发的性能问题
C#中的装箱和拆箱操作的过程随处可见,这也是.NET最基础的概念之一。这道面试题在技术笔试中出现概率比较高,希望读者能够认真阅读这一小节。
【出现频率】★★★★★
【关键考点】
装箱
拆箱
装箱和拆箱对性能的影响
【考题分析】
任何值类型、引用类型都可以和object(对象)类型之间进行转换。装箱转换是指将一个值类型显式地或者隐式地转换成一个object类型,或者把这个值类型转换成一个被该值类型应用的接口类型(interface-type)。把一个值类型的值装箱,就是创建一个object实例,并将这个值复制给这个object。装箱后的object对象中的数据位于堆中,堆中的地址在栈中,而被装箱的类型的值是作为一个拷贝赋给对象的。所谓的拆箱,就是装箱操作的反操作,复制堆中的对象至堆栈中,并且返回其值。
注意:拆箱操作将判断被拆箱的对象类型和将要被复制的值类型引用是否一致,如果不一致,将会抛出一个InvalidCastException的异常。
相对于简单的赋值而言,装箱和取消装箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个全新的对象。次之,取消装箱所需的强制转换也需要进行大量的计算。有效的减少装箱与拆箱操作是对性能提高的一个好的途径。为此微软公司在.NET中提供了泛型来解决装箱和拆箱引起的性能问题。下面笔者通过一段简单的代码来说明这个问题,代码如下所示。
namespace MyConsole
{
class UnboxPerformance
{
/*将5000000条记录写入到ArrayList并将数据在读取出来的工作做5编
其中写入和读取ArrayList的工作就是装箱拆箱的工作
最后计算出执行这个方法所消耗的时间*/
private static void RunUnbox()
{
int count;
DateTime startTime = DateTime.Now;
ArrayList myArrayList = new ArrayList();
List<int> myTlist = new List<int>();
for (int i = 5; i > 0; i--) //重复5次测试
{
myArrayList.Clear();
for (count = 0; count < 5000000; count++) //将值类型加入myArrayList数组
myArrayList.Add(count); //装箱
int j; //重新得到值
for (count = 0; count < 5000000; count++)
j = (int)myArrayList[count]; //拆箱
}
DateTime endTime = DateTime.Now;
Console.WriteLine("使用装箱拆箱的数组-- 开始时间 {0}\n结束时间 {1}\n
花费时间 {2}", startTime, endTime, endTime - startTime);
} //输出结果
/*将5000000条记录写入到泛型List集合中并将数据在读取出来的工作做5编
其中写入和读取List集合的工作不需要装箱与拆箱
最后计算出执行这个方法所消耗的时间*/
private static void RunNoUnbox()
{
int count;
DateTime startTime = DateTime.Now;
List<int> myTlist = new List<int>();
for (int i = 5; i > 0; i--) //重复5次测试
{
myTlist.Clear();
for (count = 0; count < 5000000; count++) //将值类型加入泛型数组
myTlist.Add(count);
int j; //重新得到值
for (count = 0; count < 5000000; count++)
j = myTlist[count];
}
DateTime endTime = DateTime.Now;
Console.WriteLine("使用泛型 -- 开始时间 {0}\n结束时间 {1}\n花费时间 {2}",
startTime, endTime, endTime - startTime);
} //输出结果
static void Main(string[] args)
{
RunUnbox(); //调用装箱拆箱的RunUnbox方法。
RunNoUnbox(); //调用使用泛型的RunNoUnbox方法。
Console.WriteLine("请输入任意键,结束运行");
Console.ReadLine();
}
}
}
代码运行的结果如下所示。
使用装箱拆箱的数组 -- 开始时间 2009-05-31 16:41:15 结束时间 2009-05-31 16:41:27
花费时间00:00:11.7343750
使用泛型数组 -- 开始时间 2009-05-31 16:41:27 结束时间 2009-05-31 16:41:28
花费时间00:00:01.1406250
由运行结果可以发现,即使进行同样的操作,使用装箱拆箱是使用泛型数值所花费时间的10倍。
【答案】
CLR将值类型的数据“包裹”到一个匿名的托管对象中,并将此托管对象的引用放在Object类型的变量中,这个过程称为“装箱(Boxing)”,拆箱是装箱的逆过程。对于装箱和拆箱对性能影响的解决办法是在程序中大量使用泛型进行替代。