我们将通过实际的例子以及ILDASM.exe工具去查看装箱和拆箱的过程(至于ILDASM.EXE的用法请查看MicroSoft的帮助).
一::我们想看下装箱和拆箱的对象
装箱:值类型=>引用类型.
拆箱:引用类型=>值类型
所以我们看到再整个装箱和拆箱的过程中设计到两种类型.值类型(原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct).引用类型(类,接口,数组,委托,字符串等).
实例1:阅读下面的程序,说出程序中的装箱和拆箱操作.
class sample1
{
public static void Main()
{
int i=10;
object obj=i;
Console.WriteLine(i+","+(int)obj);
}
}
答:实例1中进行了3次装箱和1次拆箱.第一次object obj=i;将i装箱;而Console.WriteLine方法用的参数是String对象,因此,i+","+(int)obj中,i需要进行一次装箱(转换成String对象),(int)obj将obj对象拆箱成值类型,而根据WriteLine方法,比较将(int)obj值装箱成引用类型。说起来这么复杂,大家看看ildasm.exe的反汇编结果(如下图),数一下Box和Unbox就很容易理解了。
如果我们将Console.WriteLine(i+","+(int)obj);改为: Console.WriteLine(obj+","+obj); 得到同样的效果,而其中仅进行一次装箱操作(object obj=i;),虽然这个程序并没有实际的意义,但是加深我们对概念的理解。
实例二:我这里我列出两个例子,装箱和拆箱对程序性能的影响
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Boxandunbox
{
class Sample
{
public static void Main()
{
int count;
DateTime startTime = DateTime.Now;
ArrayList myArrayList = new ArrayList();
// 重复5次测试
for (int i = 5; i > 0; i--)
{
myArrayList.Clear();
// 将值类型加入myArrayList数组
for (count = 0; count < 5000000; count++)
myArrayList.Add(count); //装箱
// 重新得到值
int j;
for (count = 0; count < 5000000; count++)
j = (int)myArrayList[count]; //拆箱
}
// 打印结果
DateTime endTime = DateTime.Now;
Console.WriteLine("Start: {0}"nEnd: {1}"nSpend: {2}",
startTime, endTime, endTime - startTime);
Console.WriteLine("Push ENTER to return commandline");
Console.ReadLine();
}
}
}
请看运行结果图
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Boxandunbox
{
class Sample
{
public static void Main()
{
//Code1 //Code1
//int count;
//DateTime startTime = DateTime.Now;
//ArrayList myArrayList = new ArrayList();
//// 重复5次测试
//for (int i = 5; i > 0; i--)
//{
// myArrayList.Clear();
// // 将值类型加入myArrayList数组
// for (count = 0; count < 5000000; count++)
// myArrayList.Add(count); //装箱
// // 重新得到值
// int j;
// for (count = 0; count < 5000000; count++)
// j = (int)myArrayList[count]; //拆箱
//}
//// 打印结果
//DateTime endTime = DateTime.Now;
//Console.WriteLine("Start: {0}"nEnd: {1}"nSpend: {2}",
//startTime, endTime, endTime - startTime);
//Console.WriteLine("Push ENTER to return commandline");
//Console.ReadLine();
#endregion
int count;
ArrayList myArrayList = new ArrayList();
// 构造 5000000 字符串数组
string[] strList = new string[5000000];
for (count = 0; count < 5000000; count++)
strList[count] = count.ToString();
// 重复5次测试
DateTime startTime = DateTime.Now;
for (int i = 5; i > 0; i--)
{
myArrayList.Clear();
// 将值类型加入myArrayList数组
for (count = 0; count < 5000000; count++)
myArrayList.Add(strList[count]);
// 重新得到值
string s;
for (count = 0; count < 5000000; count++)
s = (string)myArrayList[count];
}
// 打印结果
DateTime endTime = DateTime.Now;
Console.WriteLine("Start: {0}"nEnd: {1}"nSpend: {2}",
startTime, endTime, endTime - startTime);
Console.WriteLine("Push ENTER to return commandline");
Console.ReadLine();
}
}
}
结果如下图
实例二说明:实例二(1)的循环中包含一次装箱和一次拆箱(这里我均忽略两个程序打印时的装箱操作),实例二(2)则没有相应的操作。当循环次数足够大的时候,性能差异是明显的。再次提醒你别忘了ILDASM.EXE这个工具哦,分别看看,才能一窥程序的本质。否则,粗看程序实例二(2)比实例二(1)多了不少代码,更多了一个5000000(5M)的循环,就以为实例二(2)会更慢............
我们只是从实例的角度了解到装箱和拆箱对程序的性能有很大的影响.到底效率影响在什么地方??
我们知道,1:对于堆的操作效率比较低, 2:再堆上分配的内存要通过GC去收集(GC要检测到对象为Null的时候才收集),从而降低了效率.
所以再程序中我们要减少装箱和拆箱操作.如何去减少?且看下面的例子
对于“1,2,3”来说,相当于前面的“123”一样,需要经过装箱和拆箱两个操作。那么如何避免呢,其实只要向WriteLine传递引用类型数据即可,也就是按照如下的方式。
Console.WriteLine( "Number list:{0}, {1}, {2}", 1.ToString(),2.ToString(),3.ToString() );
由于“1.ToString()”的结果是String类型,属于引用类型,因此不牵扯装箱和拆箱操作。
另外.牵扯到装箱和拆箱操作比较多的就是在集合中,例如:ArrayList或者HashTable之类。
把值类型数据放到集合中,可能会出现潜在错误。例如:
{
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; }
}
public Person(string PersonName)
{
_Name = PersonName;
}
public override string ToString()
{
return _Name;
}
}
//测试代码
ArrayList arrPersons = new ArrayList();
Person p = new Person( "OldName" );
arrPersons.Add( p );
// Try to change the name
p = ( Person ) arrPersons[0] ;
p.Name = "NewName";
Debug.WriteLine( ( (Person ) arrPersons[0] ).Name );//It's "OldName"
//为什么是OldName,各位自己想想