C#学习笔记二:用实例深入理解装箱、拆箱
学习.NET的过程中,发现大多数的书都讲到了装箱(boxing)和拆箱(unboxing)的概念,至于为什么要理解装箱和拆箱?则总是一句话带过:优化程序的性能云云。至于为什么会对程序的性能产生影响,如何影响,我总感觉讲得并不透彻,当然也可能是我理解力有限。
这篇笔记,我并不打算对装箱和拆箱做全面的介绍,这些内容书上都有,csdn上也有很好的文章(请见kenli写的dotnet学习笔记一 - 装箱拆箱
http://www.csdn.net/Develop/Read_Article.asp?Id=19575),我只做简单的总结,并在此基础上引入两个例子,一个例子用ILDASM.EXE查看装箱和拆箱的过程,另外一个例子我编制一个简单例子分析正确理解装箱和拆箱对程序性能的影响。
由于在下面的例子和以后的例子我们将再次用到ILDASM,但不再给出详细的解释,因此给出MSDN关于反汇编语言的帮助信息,要查找汇编语言的命令,请在MSDN中.NET Framework/参考/类库/ System.Reflection.Emit 命名空间/OpCodes类中可以找到相关信息。
总结1:.NET中所有类型都是对象,所有类型的根是System.Object。
总结2:类型分为值类型(value)和引用类型(regerence type)。C#中定义的值类型包括:原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct)。引用类型包括:类、数组、接口、委托、字符串等。
实例一:读下列程序,你能说出其中进行了几次装箱和拆箱的操作吗?
using System;
class sample1
{
public static void Main()
{
int i=10;
object obj=i;
Console.WriteLine(i+","+(int)obj);
}
}
其中发生了三次装箱操作和一次拆箱操作。第一次object obj=i;将i装箱;而Console.WriteLine方法用的参数是String对象,因此,i+","+(int)obj中,i需要进行一次装箱(转换成String对象),(int)obj将obj对象拆箱成值类型,而根据WriteLine方法,比较将(int)obj值装箱成引用类型。说起来这么复杂,大家看看ildasm.exe的反汇编结果(如下图),就很容易理解了。
注意图中红色圆圈的标识。
如果我们将Console.WriteLine(i+","+(int)obj);
改为: Console.WriteLine(obj+","+obj); 得到同样的效果,而其中仅进行一次装箱操作(object obj=i;),虽然这个程序并没有实际的意义,但是加深我们对概念的理解。
实例二:我这里我列出两个例子,装箱和拆箱对程序性能的影响不问自知。我的机器配置是P4 1.6A,512M内存。随后会列出测试的截图,你比我更快吗?当然是的?那么告诉我吧。:(
// 例子1:boxing1.cs
using System;
using System.Collections;
namespace test1
{
class Class1
{
static void Main(string[] args)
{
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();
}
}
}
下图是boxing1.exe的测试结果:
// 例子2:boxing2.cs
using System;
using System.Collections;
namespace test2
{
class Class2
{
static void Main(string[] args)
{
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();
}
}
}
下图是boxing2.exe的测试结果:
实例二说明:boxing1.cs的循环中包含一次装箱和一次拆箱(这里我均忽略两个程序打印时的装箱操作),boxing2.cs则没有相应的操作。当循环次数足够大的时候,性能差异是明显的。再次提醒你别忘了ILDASM.EXE这个工具哦,分别看看,才能一窥程序的本质。否则,粗看程序boxing2.cs比boxing1.cs多了不少代码,更多了一个5000000(5M)的循环,就以为boxing2会更慢。。。
另外一方面,装箱和拆箱对性能的影响更偏重于大型的程序和软件,这就是我用这么多循环的原因。但你能保证你不会进行大批量的数据处理吗?
MSDN上有更实用的例子:统计大量的英文单词,当然也更加复杂,故不在此详细讲解。
http://www.microsoft.com/china/msdn/voices/csharp03152001.asp
文章的结尾处,我想你应该测试一下你对装箱和拆箱的理解:(同样来自MSDN)
看看各种方案中是否进行了装箱和拆箱的操作,各有多少次。
// 方案 1
int total = 35;
DateTime date = DateTime.Now;
string s = String.Format("Your total was {0} on {1}", total, date);
// 方案 2
Hashtable t = new Hashtable();
t.Add(0, "zero");
t.Add(1, "one");
// 方案 3
DateTime d = DateTime.Now;
String s = d.ToString();
// 方案 4
int[] a = new int[2];
a[0] = 33;
// 方案 5
ArrayList a = new ArrayList();
a.Add(33);
// 方案 6
MyStruct s = new MyStruct(15);
IProcess ip = (IProcess) s;
ip.Process();
今天就到这里吧,我也是初学,望多多指教。什么?上面测试的标答?呵呵,你应该找得到的,找不到?我会贴在评论中。