初始化
首先我们来看这段 代码1
发现什么,编译通过,运行时 call 值类型没有问题,call 引用类型却报错。
再看如下 代码2
直接编译就不通过,什么原因呢
原因在于在class的构造器会自动对所有field自动初始化,所以代码1编译可以通过,代码2方法里的私有字段确无法通过编译。
值数据直接对应的是堆栈,一初始化就自动运行值类型默认的构造器,而引用类型堆栈里的是一个空指针,指向的数据为空。
代码1等价于如下 代码3
正因为值类型将自动call constructor, 避免编程的混乱,.net也不允许值类型自定义无参构造器,因此我们编写struct的时候,也无法自定义无参构造器。
另外在类里用户如果已经有了自定义的构造器,那么编译顺序是首先编译类里字段的初始化,然后再调用自定义构造器。
等值判断和参数传递
还是看代码4
运行结果数值类型和截然不同,怎么造成的呢
所谓等值判断 所判断的就是堆栈里的内容。值类型堆栈里存放就是实际内容,所以值类型的等值判断就是判断数据相同,对于.net来说,就是通过反射,对比2个值类型的内部所有数据是否相同,全部相同则2个值类型相同
而对于引用类型,堆栈里存放的只是地址,只有地址完全相同,2个引用类型才相同,而不管实际对应的数据是什么。
所以开始的2个有相同的数值的值数据做对比返回true,而2个有相同数据的引用类型则返回false。
当2个数据传递的时候,就是1方把堆栈里的数据复制给另一方。对于值类型来说,堆栈里的就是数据,赋值消耗较大,而对于引用类型来说,堆栈里存放的只是1个地址,指向真实数据的地址。
所以当存在2个相等的引用类型,由于存在相同的地址。另一方改变数据,都将影响另一方。
而2个相等的值类型,由于堆栈存的就是数据,1方改变,对另一方没有任何影响。
因此第3个判断为2个值类型,1方改变不影响另一方,返回false,第4个判断2个引用类型,双方一起变化,返回true。
注意:string有点不一样,是引用类型,但是确是数值恒定的引用类型,CLR特别开辟了一个驻留表来保存所有的string数值,所以传递的时候传递的是指针,但数据改变不是影响原来的数据,而是新开一个空间给新数据.
转换
转换可以分2大类
第1类为运算符转换,转换的时候调用类里的转换运算符,又包含implicit operator(隐性转换) explicit operator(显性转换),如:int i=1;long l=1;l=i;i=(int)l;
这里l=i就是隐性转换,i=(int)l为显性转换。
第2类为由于继承关系而产生的转换,子类向父类(或接口)的转换(传递)
如:Employee e=new Employee(); IEmployee ie=e;
这里传递的是指针。虽然语法跟上面运算法转换一样,但实际工作并不一样。
注意:当转换失败的时候运行将会报错。
另外还有专门给类转换用的as转换符,语法如:IEmployee ie=e as IEmployee;
as转换符不支持operator的转换,只传递指针地址。
另外:当as转换失败的时候,并不会报错,而是传递一个空指针,得到1个null数据。
值类型与引用类型之间的转换
上节提到的转换时值类型与值类型,引用类型和引用类型之间的转换,如果是值类型与引用类型之间的转换呢
如object与int之间的转换。
1:值类型转为引用类型,首先将堆栈中值的数据复制到堆里,然后将指针传给引用类型,此步骤称为box(装箱)
2:引用类型转为值类型。根据引用指针获得数据内容,然后把堆中的数据拷贝到堆栈,此步骤称为unbox(拆箱)
显然从这里看的出box与unbox消耗大量资源,及其影响效率,所以对于未知类型的数据应该尽量用泛型,避免使用类似ArrayList等类。
参考
《applied microsoft .net framework Programming》
《Professional c#》