第 5 章 基元类型、引用类型和值类型

第 5 章 基元类型、引用类型和值类型

本章内容:

5.1 编程语言的基元类型

基元类型

编译器直接支持的数据类型称为 基元类型 基元类型直接映射到 Framework 类库(FCL)中存在的类型。

只有在转换“安全”的时候,C#才允许隐式转型。

5.2 引用类型和值类型

值类型和引用类型的一些区别

  1. 值类型有两种表示形式:未装箱和已装箱,引用类型总是已装箱。
  2. 值类型从System.ValueType派生,重写了Equals和GetHashCode方法。
  3. 由于不能将值类型作为基类型来定义新的值类型或者新的引用类型,所以不应在值类型中引入任何新的虚方法。所有方法都不能是是抽象的,所有方法都隐式密封(不可重写)。
  4. 值类型的变量总是包含基础类型的一个值,且成员初始化为0;而引用类型的变量包含堆中对象的地址,创建时默认初始化为null 。
  5. 将值类型变量赋给另一个值类型变量会执行逐字段复制,而将引用类型变量赋给另一个引用类型变量只复制内存地址。
  6. 引用类型的多个变量可能引用堆中同一个对象,而值类型变量是独立的。
  7. 由于未装箱的值类型不在堆上分配,一旦定义了该类型的一个实例的方法不再活动,为它们分配的存储就会被释放,而不是等着进行垃圾回收。

5.3 值类型的装箱和拆箱

装箱过程

  1. 在托管堆中分配内存。分配的内存量是值类型各字段所需的内存量,还要加上托管堆所有对象都有的两个额外成员(类型对象指针和同步索引块)所需的内存量。
  2. 值类型的字段复制到新分配的堆内存。
  3. 返回对象地址。现在该地址是对象引用;值类型成了引用类型。

C# 编译器检测到上述代码是向要求引用类型的方法传递值类型,所以自动生成代码对对象进行装箱。

拆箱

CLR 分两步完成复制。
第一步获取已装箱对象中各个字段的地址。这个过程称为拆箱
第二步将字段包含的值从堆复制到基于栈的值类型实例中。

如果重写的虚方法要调用方法在基类中的实现,那么在调用基类的实现时,值类型实例会装箱

5.3.2 对象相等性和同一性

同一性

两个引用是否指向同一个对象。

相等性

在同一性的基础上增加了每个实例字段的比较。

5.4 对象哈希码

重写 Equals 就必须重写 GetHashCode ,确保相等性算法和对象哈希码算法一致。

5.5 dynamic 基元类型

使用dynamic类型可以调用成员操作,比如字段、属性、方法等。编译器会生成特殊的IL代码(有效载荷),在运行时根据实际类型执行相应操作。

如果字段,方法参数或方法返回值的类型是 dynamic,编译器会将该类型转换为 System.Object ,并在元数据中向字段、参数或返回类型应用 System.Runtime.CompilerServices.DynamicAttribute 的实例

如果局部变量被指定为 dynamic,则变量类型也会成为 Object,但不会向局部变量应用 DynamicAttribute

编译器通常不允许将表达式从 Object 隐式转型为其他类型,必须显式转型。但是,编译器允许使用隐式转型语法将表达式从 dynamic 转型为其他类型。

dynamic 和 var 区别

  • var 声明局部变量只是一种简化语法,它要求编译器根据表达式推断具体数据类型。var关键字只能在方法内部声明局部变量。
  • dynamic 关键字可用于局部变量、字段和参数。
  • 表达式不能转型为var,但能转型为dynamic。
  • 必须显示初始化用var声明的变量,但无需初始化用dynamic 声明的变量。