CLR-via-C#笔记——第五章

基元类型、引用类型和值类型

《CLR via C#》第五章:基元类型、引用类型和值类型

编程语言的基元类型

编译器直接支持的数据类型称为基元类型(primitive type)。
对基元类型执行的许多算术运算都可能造成溢出,可以使用checked和unchecked操作复来提供溢出检查。

  • 尽量使用有符号数值类型。
  • 如果可能发生不希望的溢出,把代码放到checked代码块中,并捕捉OverflowException。
  • 将允许溢出的代码显式放到unchecked块中。

引用类型和值类型

CKR支持两种类型:引用类型和值类型。

  • 引用类型必须从托管堆分配。
  • 对上分配的每个对象都有一些额外成员,这些成员必须初始化。
  • 对象中的其他字节总是设为零。
  • 从托管堆分配对象时,可能强制执行一次垃圾回收。

值类型的实例一般在线程栈上分配。
值类型的实例不受垃圾回收器的控制。
所有的值类型都隐式密封,目的是防止将值类型用作其他引用类型或值类型的基类。
C#中,用struct声明的类型是值类型,用class生命的类型是引用类型。
将类型声明为值类型需要满足下面条件:

  • 类型具有基元类型的行为,没有成员会修改类型的任何实例字段。如果类型没有提供更改其字段的成员,就说改类型不可变(immutable)。对于许多值类型,建议将其全部字段标记为readonly。
  • 类型不需要从其他任何类型继承
  • 类型也不派生出任何其他类型
    除此以外,还必须满足下面任意条件:
  • 类型实例较小(16字节或更小)
  • 类型实例较大,但不作为方法实参传递,也不从方法返回。

值类型和引用类型的区别:

  • 值类型对象有两种表示形式:未装箱和已装箱。引用类型总是处于已装箱形式。
  • 值类型从System.ValueType派生。该类型提供了与System.Object相同的方法,但重写了Equals方法和GetHashCode方法。定义自己的值类型时也应重写这两个方法,提供显式实现。
  • 值类型中不应引入任何新的虚方法。所有方法都不能是抽象的,所有方法都隐式密封(不可重写)。
  • 引用类型的变量包含堆中对象的地址。值类型变量总是包含其基础类型的一个值。
  • 将值类型赋值给另一个值类型变量,会执行逐字段的复制。将引用类型的变量复制给另一个引用类型的变量只复制内存地址。
  • 两个或多个引用类型变量能引用堆中同一个对象,对一个变量执行的操作可能影响到另一个变量引用的对象。值类型变量自成一体,对值类型变量执行的操作不会影响另一个值类型变量。
  • 由于未装箱的值类型不在堆上分配,一但定义该类型的一个实例方法不再活动,分配的内存就将会被释放,而不是等待垃圾回收。

值类型的装箱和拆箱

将值类型转换成引用类型要使用装箱机制。
值类型实例进行装箱的过程:

  • 在托管堆中分配内存。内存量为值类型各字段所需的内存两,加上托管堆所欲对象都有的两个额外成员所需的内存量(类型对象指针和同步块索引)。
  • 值类型的字段复制到新分配的堆内存。
  • 返回对象地址。
    引用对象类型拆箱:
  • 取得已装箱对象中各个字段的地址。(拆箱,拆箱其实是获取指针的过程,拆箱不要求在内存中复制任何字段,但拆箱过程往往紧跟着一次字段复制)
  • 将字段字段包含的值从堆复制到基于栈的值类型实例中。(这一步不算拆箱过程)

对象的相等性和同一性
Object提供了静态方法ReferenceEquals。
实现自己类型时,重写的Equals要符合相等性的4个特性:

  • 自反(x.Equals(x)必为true)
  • 堆成(x.Equals(y)等于y.Equals(x))
  • 可传递(x.Equals(y)为true,y.Equals(z)为true,则x.Equals(z)必为true)
  • 一致性(比较的值不变,Equals返回值也不变)

对象哈希码

如果重写了Equals方法,还应重写GetHashCode方法。
选择算法计算类型实例哈希值时,遵守以下规则:

  • 算法要提供良好的随机分布,使哈希表获得最佳性能。
  • 可在算法中调用基类的GetHashCode,并包含它的返回值。但不要调用Object或ValueType的GetHashCode方法。
  • 算法至少使用一个实例字段。
  • 理想状态下,算法使用的字段应该不可变。
  • 算法执行速度尽量快。
  • 包含相同值的不同对象应返回相同哈希码。

永远不要对哈希码进行持久化操作。

dynamic基元类型

允许将表达式的类型标记为dynamic,可以用dynamic表达式/变量调用成员。使用dynamic表达式/变量时,编译器生成特殊的IL代码,成为payload(有效载荷)。运行时,payload代码根据dynamic表达式/变量引用的对象实际类型来决定具体执行的操作。
dynamic和var不同,var只是一种简化语法,编译器根据表达式推断具体数据类型,只能在方法内部声明局部变量。dynamic关键字可用于局部变量,字段和参数。表达式不能转型为var,但能转型为dynamic。