重温C#基础知识(二)
二、值类型和引用类型
1、值类型和引用类型的基类
值类型包括有结构(Struct)、枚举(Enum)整型、布尔型等等。
引用类型包括有数组(Array)、委托(MulticastDelegate)、接口、异常类等等。
2、值类型和引用类型的特点和区别
(1)值类型的特点
可以说值类型就是一个轻量级的引用类型,相比引用类型值类型的性能更加好。
1)值类型可以在线程栈、托管上分配。具体来说,值类型就是在声明(值类型)变量的位置上存储的。当(值类型)变量在引用类型类的对象中时,该变量的值总是和对象的其他数据分配在托管堆上。只有当变量声明在方法内部和方法参数上时,才分配在线程栈上。其中声明在方法内部的值类型变量就是局部变量。
2)每次声明值类型变量是不需要进行内存分配。值类型的实例也就是变量不受垃圾回收器的控制减少需要进行垃圾回收的次数。
3)自定义的值类型不能有基类,但可以实现一个或者多个接口。同样值类型不能用于作其他引用类型和值类型的基类,因为值类型是隐式密封的(Sealed),也意味着值类型不能派生出其他类型。由于值类型不能用于派生其他类型相比引用类型,值类型就少了每个引用类型对象开头所包含的用于标识对象的实际类型的数据块和一些其他数据,这也意味着初始化值类型变量时不用多进行对象开头的一些数据的初始化工作。
4)因为值类型不能作为基类不能派生出其他类型,所以自定义的值类型不能包含有虚方法、抽象方法。所有的方法都是隐式密封的不可在派生类中重写的。
5)值类型有两种表现形式:未装箱和已装箱两种形式,而引用类型只有一种形式就是已装箱形式。正因为值类型有两种形式所以值类型可以通过装箱成为引用类型。
6)执行值类型的复制时会执行一次逐字段的复制操作,在值类型实参的传值(以传值的方式传值)过程中就是执行一次逐字段的复制操作。
7)C#不允许为值类型定义一个Finalize方法即使在值类型处于已封装的状态下定义该方法CLR也不会调用该方法,该方法也不会收到内存回收的通知。
(2)引用类型的特点
1)引用类型对象只能在内存的托管堆上分配而且每次分配可能要强制执行一次垃圾回收操作。其中分配的包括对象开头用于标识对象实际类型的数据块和其他数据(类型对象指针,同步块索引);
2)引用类型变量的值只是堆上一个对象的地址,也就是说引用类型变量分配在线程栈上,但是该变量在线程栈上的值只是一个引用类型对象在堆上的一个内存地址。意思就是说创建引用类型对象时是将该对象分配到托管堆上的,但是通过一个分配在线程栈上的变量来引用该对象。
3)在执行引用类型对象的复制操作时只复制分配在线程栈上变量的值(指向分配在托管堆上的引用类型对象的内存地址),也就是只复制一个内存地址并不执行引用类型对象的全部复制操作。
4)因为引用类型可以作为其他类型的基类,所以引用类型可以包含虚方法、抽象方法等。
5)多个引用类型对象可以同引用一个分配在托管堆山的对象,但是更改其中的一个对象其他对象所引用的对象也都会发生变化。
(3)值类型和引用类型的对比
3、装箱和拆箱
(1)装箱的过程
装箱:就是将一个值类型变量通过装箱的机制转换成一个引用类型。
必须装箱的情况:
1)当值类型中重写了Equlas(),GetHashCode(),ToString()方法并在里面调用了该方法在基类中的实现的时候,调用基类的实现时,值类型实例就会进行装箱,以便通过this指针将一个托管堆中的对象的引用传给基方法。如果值类型中重写了上述三个方法但是并没有在方法里面调用基类方法的实现,这个时候不需要进行装箱的操作。
2)调用一个非虚的、继承的方法(从System.Object继承的)时要对值类型进行装箱操作。在调用一个非虚的方法时不需要进行装箱的操作。
3)将值类型的一个未装箱实例转换为类型的某个接口时,要对实例进行装箱操作。因为接口必须包含对托管他上一个对象的引用。
(2)拆箱的过程
拆箱:就是获取已装箱对象中各个字段的地址的过程。并不包含将已装箱对象在托管堆中各个字段复制到基于栈的值类型中去。所以拆箱后就紧接着一次字段的复制操作。