C#的类型系统
我们知道C#是一种强类型语言,它要求使用者必须先对所有变量进行声明类型之后才能使用。根据官方文档中的说明,我们知道类型中可以存储以下信息。
类型中可存储的信息包括以下项:
- 类型变量所需的存储空间。
- 可以表示的最大值和最小值。
- 包含的成员(方法、字段、事件等)。
- 继承自的基类型。
- 它实现的接口。
- 允许执行的运算种类。
对于编译器来说,编译器将类型信息作为元数据嵌入可执行文件中。 公共语言运行时 (CLR) 在运行时使用元数据,以在分配和回收内存时进一步保证类型安全性。
基元类型
基元类型就是指C#中的内置类型,如object,int,char,string等,完整的数据类型列表可以查看官网文档。C#通过关键字对类型进行映射,使其自动的映射到.NET类型。即使一种类型有多种写法也并不会有多种效果,最后都是映射到唯一的.NET类型。简言之关键字只是一种.NET类型的别名。
通用类型系统
C#中的类型系统,有两点基本原则。
- 支持继承原则,即所有的类型最终都派生自单个基类-System.Object。
- 将每种类型分为值类型与引用类型。具体的,所有使用Struct定义的类型都是值类型,所有使用Class定义的类型都是引用类型
值类型和引用类型
值类型
从基本的定义来看,值类型值类型进行运算时,会通过实参传递的方法并返回结果来复制变量值,如果值类型中包含引用类型则同样会对实例的引用进行复制。对于值类型来说,他的值永远是该类型的一个实例。
引用类型
在创建对象时,引用类型会在托管堆上分配内存,变量只保留对对象位置的引用,如果没有值则包含null,直到对它进行赋值。对于引用类型来说,它的值永远是一个引用。
装箱和拆箱
对于值类型来说,有时候我们不需要使用它的值,而仅仅只是想得到一个它的引用,这时候,C#就为我们提供了一个名为装箱的机制。以下面的代码举例
int i = 10;
object b = i;
int j = (int) b;
对于i来说,它创建了一个值类型变量,值为10,而对于b来说,它在赋值过程中也同样创建了一个值类型对象,然后将其引用赋值给b。这个过程就是装箱。对于j来说,编译器通过(int)知道了要将b转换为一个值类型的int,如果使用了错误了类型则会抛出一个错误。最后同样的,他会返回一个值类型的复制赋值给j。这个过程就是拆箱。
通过上述的过程,我们可以发现,装箱与拆箱就是值类型与引用类型间的一种强制类型转换。需要留意的是,频繁的拆箱装箱同样会增大程序的性能开销。
具体应用时需要注意的地方
- 引用类型做参数传递时,引用传递参数是原变量的指针,引用传递参数和原变量的内存地址相同,相应的方法中对引用传递参数的修改会改变原变量。
- null 是引用类型变量的默认值。 普通值类型不能为 NULL,可为空的值类型除外。
- 值类型可以是以下种类:结构体、枚举、可空值类型以及内置值类型。
- 引用类型可以是以下种类:类、接口、委托、记录以及内置引用类型。
栈区和堆区
- 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
- 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
- 值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。