C#之装箱和拆箱
在实际编码过程中,有时候会出现装箱和拆箱操作。下面就类分别认识一下:
需要注意的是,类型转换和这个是不同的。Convert方法并没有发生装箱和拆箱操作,而是类型转换,包括int.parse等等。
装箱,是把值类型拷贝了一个副本放在堆内存中。
拆箱,在引用类型直接找到值类型存储的位置(Person对象是引用类型,但其Age属性是值类型,也存储在堆内存中),实际上我们往往拆箱后会用一个值类型变量接收它。
例1:
1 int n = 10; 2 Console.WriteLine(n); 3 object o = n;//一次装箱 4 Console.WriteLine((int)o); 5 Console.WriteLine(o);//这里输出的是字符串"10",相当于Console.WriteLine(o.ToString());
上面的代码中,第2句话并没有发生装箱,因为Console.WriteLine()方法有一个int类型重载;第3句话发生一次装箱;而第4句话(int)o发生一次拆箱,拆箱后的值被其values接收,如果没有Console.WriteLine()方法有一个int类型重载的话,会马上再次装箱,因为这个重载,没有发生装箱;最后1句话,输出的是字符串"10",相当于Console.WriteLine(o.ToString())。所以在判断一句话是否发生装箱和拆箱要结合构造函数来看。
例2:
1 int n = 10; 2 3 IComparable com = n; 4 5 int m = (int)com; 6 7 Console.WriteLine(m);
上面的代码中,第二句话发生了装箱,第三句话发生了拆箱,因为int类型实现了IComparable接口。
例3:
1 int d = 999; 2 object o = d; //装箱 3 4 //装箱时使用什么类型,拆箱时也必须使用同样的类型。否则报异常 5 double d1 = (double)o; //拆箱
6 Console.WriteLine(d1);
例3中,编译会报错,以为在第2句话装箱前是int类型,而拆箱时确是double。需要注意的是,装箱前是什么类型,拆箱后也必须是什么类型,否则会报错。
例4:
1 int n = 100; 2 M1(n); 3 4 string x = "a"; 5 double d1 = 10; 6 string y = "b"; 7 int n1 = 9; 8 string z = "c"; 9 string full = x + d1 + y + n1 + z; 10 Console.WriteLine(full); 11 12 //定义函数如下: 13 static void M1(double d) 14 { 15 Console.WriteLine(d); 16 } 17 18 static void M1(object o) 19 { 20 Console.WriteLine(o); 21 }
上面的代码到底发生了几次装箱和拆箱呢?首先看M1()方法,经反编译得知M1((double) n);由此可见,M1()方法并没有去找object类型的重载,而是找了与之相近的double类型重载,所以没有发生装箱。第9句话经反编译得知Console.WriteLine(string.Concat(new object[] { x, d1, y, n1, z }));它将double类型变量d1和int类型变量n1装箱成object类型。所以上面的代码只发生了2次装箱。
装箱和拆箱带来的性能影响:
日常编码中应避免装箱和拆箱,因为他们会带来性能的损耗。看下面的代码:
1 ArrayList aList = new ArrayList(); 2 Stopwatch sw = new Stopwatch(); 3 sw.Start(); 4 for (int i = 0; i < 100000; i++) 5 { 6 aList.Add(i); //Add方法每添加一次发生一次装箱 7 } 8 sw.Stop(); 9 Console.WriteLine("用时:"+sw.Elapsed);
运行结果:
我们换另一种:
1 List<int> aList = new List<int>(); 2 Stopwatch sw = new Stopwatch(); 3 sw.Start(); 4 for (int i = 0; i < 100000; i++) 5 { 6 aList.Add(i); 7 } 8 sw.Stop(); 9 Console.WriteLine("用时:" + sw.Elapsed);
运行结果:
由此可见,性能影响还是挺大的。
总结:
一)装箱和拆箱的判断依据:
1.发生在值类型和引用类型之间;
2.具备父子类的关系。
二)判断一句话是否发生装箱和拆箱要结合构造函数来看;
三)装箱前是什么类型,拆箱后也必须是什么类型,否则会报错;
四)写代码的过程中应尽量避免装箱和拆箱带来的性能消耗。