引用类型和值类型
CLR 支持两种类型:引用类型和值类型。
引用类型( reference type):从托管堆上分配。
① 内存必须从托管堆中分配
② 每个在托管堆中分配的对象都有一些与之关联的额外附加成员必须被初始化。
③ 从托管堆中分配的对象可能会导致执行垃圾收集。
如果我们代码中的每个类型都是引用类型的话,应用程序的性能将会大打折扣。设想如果每使用一个 Int32 值,系统都会出现一次内存分配,应用程序的性能该会有多糟糕。因此,CLR 提供了一直称作值类型的“轻量级”类型
值类型:通常分配在线程的堆栈上(也可以被嵌入到一个引用类型的对象中)
值类型实例的变量不包含指向实例的指针——变量本身即包含了实例所有的字段。因为变量本身包含了实例的所有字段,所以操作实例时也就无需再解析指针引用。
值类型实例不受垃圾收集器的控制。因此也减少了托管堆的压力,以及应用在整个生存期中需要垃圾回收的次数。
在设计自己的类型时,是将他们定义为值类型?还是引用类型?在满足一下所有表述时我们就可以考虑将类型声明为值类型。
① 该类型的行为类似于基元类型。
② 该类型不需要继承自任何其他类型。
③ 该类型不会被其他类型继承。
④ 该类型的实例不会频繁地用于方法的参数传递。默认情况下,参数以传值的方式传递,这会导致值类型实例中的字段被拷贝,从而损伤应用程序性能。
⑤ 该类型的实例不会作为方法的结果频繁地返回。从方法中返回一个值类型也会导致实例中的字段被拷贝到调用者分配的内存中,因此也会损伤应用程序的性能。
⑥ 该类型的实例不会被频繁地用于诸如 ArrayList 、 Hashtable之类的集合中。这些管理一组通用对象集合的类会对值类型实例执行装箱操作,这将导致额外的内存分配,以及额外的内存拷贝操作,从而也会损伤应用程序的性能。
值类型和引用类型的差别:
① 值类型对象有两种表示:一种是未装箱( unboxed )形式,一种是装箱( boxed )形式。引用类型总是装箱形式。
② 值类型都继承自 System.ValueType。 System.ValueType 没有在 System.Object 之外定义任何新的方法。但是,System.ValueType 重写了其中的 Equals 方法。当两个 ValueType 对象的字段相互匹配时, Equlas 方法会返回 true。另外,System.ValueType 还重写了 GetHashCode 方法,它会使用对象的实例字段并根据一定的算法来产生一份散列码值。当定义自己的值类型是,我们应该重写 Equals 和 GetHashCode方法。
③ 因为我们声明的值类型或引用类型不能以一个值类型作为基类,所以我们不应该向值类型中引入任何新的虚方法。值类型中更不可以有任何的抽象方法,所有的方法都隐含为密封( sealed )方法(即不能重写 );
④ 引用类型变量包含着对象在托管中的内存地址。默认情况下,当一个引用类型变量被创建时,它被初始值为 null ,表示该引用类型变量目前没有指向一个有效的对象。试图使用 null 引用类型变量会抛出一个 NullReferenceException 异常。相反,值类型变量总是包含一个符合它类型的值,并且所有的字段都被初始化为 0 值。当访问一个值类型是,不可能产生NullReferenceException 异常。
⑤ 当将一个值类型变量赋值给另一个值类型变量时,会进行一个 “字段对字段”的拷贝。但当将一个引用类型变量赋值给另一个引用类型变量时,只会拷贝内存地址。
⑥ 因为前一点,两个或多个引用类型变量可以指向托管堆中的同一个对象,这样对一个变量的操作将会影响到其它变量引用的对象。另一方面,每个值类型变量都有一份自己的“对象”数据拷贝,对一个值类型变量的操作不可能影响到另一个。
⑦ 因为未装箱值类型没有分配在托管堆上,所以一旦定义该类型实例的方法不再处于活动状态,为它们分配的存储空间就会立即释放。这意味着值类型实例在内存被回收时不可能收到任何通知(Finalize 方法)。