C#值类型与引用类型

408 阅读4分钟

从以下几个方面总结一下C#的值类型与引用类型:

  • 定义
  • 值类型与引用类型的区别
  • 值类型与引用类型的选择
  • 拆装箱
  • 如何避免拆装箱

.Net框架最主要的两个组成部分是CLR和框架类库(FCL),而CLR最主要的两个组成部分部分是CTS(公共类型系统)和CLS(公共语言规范)。简单来说,CTS就是一种规范,它允许遵守此规范的编程语言可以存在什么类型,每种类型是否可以包含成员、以及可以包含哪些成员。官方给出的CTS作用如下:

  • 建立用于跨语言执行的框架。
  • 提供面向对象的模型,支持在 .NET 实现上实现各种语言。
  • 定义处理类型时所有语言都必须遵守的一组规则。
  • 提供包含应用程序开发中使用的基本基元数据类型(如 Boolean、Byte、Char 等)的库。

故,C#中的值类型与引用类型就遵循此规范。


定义

  • C# 中的数据类型可以简单的分为值类型(结构、枚举)和引用类型(类、接口、字符串、数组、委托、指针)。
    • 值类型变量或常量的内容仅仅是一个值,例如整数int,小数float/double,布尔值等,换句话说就是,变量引用的位置就是值在内存中的位置。
    • 引用类型比较复杂,它友两部分组成:对象和对象引用。而引用类型的变量中的内容存储的是对‘数据’存储位置的引用,不是直接存储‘数据’。
  • 存储开销:
    • 值类型所占内存大小就是存储字段所需内存。例如Book所需内存就是price、title、author内存的和。
    • 引用类型要求为引用和对象单独分配存储空间。对象除占用了和字段一样的字节数之外,还需要额外的管理空间开销。

值类型实例的赋值总是会进行实例复制。 引用类型变量的赋值只会复制引用。

查阅https://referencesource.microsoft.com,会发现 数值类型、布尔类型、字符类型 其实都属于Strust类型


值类型与引用类型的区别

  1. 所有值类型都隐式派生自System.ValueType,它是System.Object的子类,该类确保值类型所有的的成员全部分配在栈上。(以下三个除外)
    • 结构体如果含有引用类型成员,该成员会牵扯到堆的分配
    • 静态类型,如果一个变量时静态的,那么,无论她是什么类型,都会分配到加载堆上
    • 局部变量被捕获升级为密封类(闭包中涉及到)
      所以,“值类型都分配在栈上,引用类型都分配在堆上”是不准确的
  2. 引用类型初始值为null,值类型初始值为0.
  3. 值类型不能被继承,引用类型则可以
  4. 值类型的声明周期在其定义域内,离开定义域之后即被销毁,而引用类型无法确定何时被销毁

装箱与拆箱

装箱与拆箱就是值类型与引用类型之间的转换

装箱过程

int i = 1;
object o = i;
  1. 在堆中申请内存,内存大小为值类型大小,载加上额外的固定空间(类型对象指针和同步块索引)
  2. 将值类型的字段值拷贝到新申请的内存中
  3. 返回新引用对象的内存地址

装箱需要比原数据更多的空间, 因为它需要两个引用类型的标准配置:类型对象指针和同步块索引

拆箱过程

int b = (int) o;
  1. 检查引用类型是否为null,否则抛出NullReferenceException异常。检查对象实例,确保它是给定值类型的一个装箱值。否则抛出InvaildCastException异常。
  2. 将该值的实例复制到值类型变量中。

与拆箱比较,装箱的性能消耗更大,因为引用对象的分配更复杂,成本也就更高,值类型分配在堆栈上,分配和释放的效率都很高。