装箱和拆箱示例(C-)
装箱和拆箱示例(C-)////1 同是引用类型 ////将子类赋值给父类 //Person p = new Student(); ////这个是进行了显示类型转换 不叫拆箱 //Student stu = (Student)p; ////原因在于它们都是引用类型 因此 ////只存在类型转换的关系 ////int类型为什么可以装箱到object类型 ////但不能装箱到string类型或Person类型 ////原因在于:object类型是int类型的父类(值类型也继承自object) //2 通过方法将值类型转换为引用类型 int n = 10; //不是装箱 只是进行了隐式类型转换 string s1 = Convert.Tostring(n); //不是拆箱 只是进行了隐式类型转换 //只是从意义上而不是从内存上进行转换 int m = int.Parse(s1); //原因在
1)了解装箱拆箱的概念
2)掌握什么情况下会发生装箱 拆箱操作(2-7)
3)通过示例查看有无装箱拆箱操作的性能差别(查看消耗时间)
装箱拆箱的定义:1) 装箱:将值类型转换为引用类型 是隐式进行的
2) 拆箱:将引用类型转换为值类型 是显示进行的
因为每进行一次装箱或拆箱操作都会涉及一次内存的拷贝动作 因此大量频繁地进行装箱拆箱的操作会大幅影响程序的性能
标准装箱拆箱在C#中object类型是所有类型的基类 因此值类型 引用类型都可以赋值给object类型
//声明了值类型 n
int n = 8;
//进行了装箱操作
//将值类型 n 进行了隐式(自动)转换
//将其赋值给了引用类型的 o
object o = n;
//进行拆箱操作
//将引用类型的 o 进行了显示(强制)转换
//将其赋值给了 值类型的 m
int m = (int)o;
Console.WriteLine(m);
Console.ReadKey();
反编译查看底层的装箱拆箱操作
只进行类型转换而无装箱拆箱////1 同是引用类型
////将子类赋值给父类
//Person p = new Student();
////这个是进行了显示类型转换 不叫拆箱
//Student stu = (Student)p;
////原因在于它们都是引用类型 因此
////只存在类型转换的关系
////int类型为什么可以装箱到object类型
////但不能装箱到string类型或Person类型
////原因在于:object类型是int类型的父类(值类型也继承自object)
//2 通过方法将值类型转换为引用类型
int n = 10;
//不是装箱 只是进行了隐式类型转换
string s1 = Convert.Tostring(n);
//不是拆箱 只是进行了隐式类型转换
//只是从意义上而不是从内存上进行转换
int m = int.Parse(s1);
//原因在于:字符串类型与int类型在内存上不存在"交集"
//根本无法进行类型转换 而object与int可以转换的原因是
//因为任何类型都是继承自object类型
可反编译在IL模式下查看是否有 box与unbox操作
拆箱时 必须使用装箱时的类型来拆箱
装箱的时候 使用什么类型来装箱 拆箱的时候还必须使用原来的类型来拆箱 否则会报异常:指定的转换无效
使用原类型进行拆箱操作
int n = 10;
object obj = n; //进行了装箱
double d = (int)obj;//进行了拆箱
//只要保证拆箱的类型与装箱时的类型一样即可
//最后赋值给 double 的 d 只是进行了隐式类型转换
//与装箱拆箱操作无关
Console.WriteLine(d);
//============================
//无法进行拆箱 会报异常:指定的转换无效
//装箱时类型为 int 拆箱时类型为 double
double f = (double)obj;
Console.WriteLine(f);
Console.ReadKey();
方法重载时 参数有该类型的重载 不叫装箱拆箱
int n = 10;
//只是进行了类型转换
//没有发生装箱操作
string m = n.ToString();
//没有发生拆箱操作
int num = Convert.ToInt32(m);
Console.WriteLine(num);
Console.ReadKey();
接口与值类型之间的装箱与拆箱
值类型也可以继承接口 如int32实现(继承的意思)了3个接口与两个泛型接口
int n = 8;
//进行了装箱操作
IComparable ic = n;
//进行拆箱操作
int m1 = (int)ic;//拆箱后赋值给了原类型变量
//拆箱后先进行了隐式类型转换
//然后赋值给了float类型的变量
float m2 = (int)ic;
Console.WriteLine(m1 " " m2);
Console.ReadKey();
进行字符串连接时也会发生装箱 拆箱操作
string s1 = "a";
string s2 = "b";
int n3 = 10;//装箱
float n4 = 9.9f;//装箱
double n5 = 99.9;//装箱
//Concat()将参数进行连接
//共进行了3次装箱操作
string result = string.Concat(s1 s2 n3 n4 n5);
Console.WriteLine(result);
Console.ReadKey();
字符串连接会发生装箱操作
装箱拆箱消耗性能问题通过向ArrayList与List集合中添加10万个数字查看有无装箱时消耗时间的差别
//声明时就设置好容量 可节省性能
int capacity = 1000000;
ArrayList arList = new ArrayList(capacity);
List<int> list = new List<int>(capacity);
Console.WriteLine("共进行 {0} 次的添加元素" capacity);
Console.WriteLine("ArrayList集合 会发生装箱操作");
Stopwatch sw = new Stopwatch();
sw.Start();//开始计时
for (int i = 0; i < capacity; i )
{
arList.Add(i);
}
sw.Stop();
Console.WriteLine("共消耗 {0} 毫秒" sw.ElapsedMilliseconds);
Console.WriteLine("");
Console.WriteLine("List泛型集合 不会发生装箱操作");
sw.Reset();//重置对象
//sw.Restart();//重启对象
sw.Start();//开始计时
for (int i = 0; i < capacity; i )
{
list.Add(i);
}
sw.Stop();
Console.WriteLine("共消耗 {0} 毫秒" sw.ElapsedMilliseconds);
Console.ReadKey();
两种集合所耗时间
使用集合时 尽量使用泛型集合 以减少操作时中间可能存在的消耗
小结:
1)装箱拆箱(以下以"此操作"代指)发生在引用类型与值类型之间
2)即使是值类型与引用类型如果转换方法有该类型作为参数 也不会发生此操作
3)接口与值类型 字符串连接上都会发生此操作