C# 中的基元类型、值类型和引用类型 1. 基元类型(Primitive Type)
编译器直接撑持的类型称为基元类型。基元类型可以直接映射到 FCL 中存在的类型。例如,int a = 10 中的 int 就是基元类型,其对应着 FCL 中的 System.Int32,上面的代码你完全可以写作System.Int32 a = 10,编译器将生成完全形同的 IL,也可以理解为 C# 编译器为源代码文件中添加了 using int = System.Int32。
1.1 基元类型的算术运算的溢出检测对基元类型的大都算术运算都可能产生溢出,例如
byte a = 200; byte b = (Byte)(a + 100);//b 此刻为 4 上面代码生成的 IL 如下
从中我们可以看出,在计算之前两个运算数都被扩展称为了32位,然后加在一起是一个32位的值(十进制300),该值在存到b之前又被转换为了Byte。C# 中的溢出查抄默认是*的,所以上面的运算并不会抛出异常或孕育产生错误,也就是说编译器生成 IL 时,默认选择加、减、乘以及转换操纵的无溢出查抄版本(如上图中的 add 命令以及conv.u1都是没有进行溢出查抄的命令,其对应的溢出查抄版天职别为add.ovf和conv.ovf),这样可以使得代码快速的运行,但前提是开发人员必需保证不产生溢出,或者代码能够预见溢出。
C#中控制溢出,可以通过两种方法来实现,一种全局设置,一种是局部控制。全局设置可以通过编译器的 /checked 开关来设置,局部查抄可以使用 checked/unchecked 运算符来对某一代码块来进行设置。进行溢出查抄后如果产生溢出,会抛出 System.OverflowException 异常。通过上述设置后编译器编译代码时会使用加、减、乘和转换指令的溢出查抄版本。这样生成的代码在执行时要稍慢一些,因为 CLR 要查抄这些运算是否产生溢出。
使用溢出查抄
最佳实践: 在开发措施时打开 /checked+ 开关进行调试性生成,这样系统会对没有显式符号为 checked 或 unchecked 的代码进行溢出查抄,此时产生异常便可以轻松捕捉到,及时修正代码中的错误 ,正式颁布时使用编译器的 /checked- 开关,确保代码能够快速运行,不会孕育产生溢出异常。
2. 值类型和引用类型 CLR 撑持两种类型:值类型和引用类型,下面引用 MSDN 对两者的界说:
值类型直接包罗它的数据,值类型的实例要么在仓库上,要么在内联布局中。与引用类型对比,值类型更为"轻",因为它们不需要在托管堆上分配内存,亦不受垃圾回收器的控制,无需进行垃圾回收,C#中的值类型都派生自System.ValueType ,值类型主要包孕两种类型:布局 和 枚举, 布局可以分为以下几类:
数值类型
bool 类型
char 类型
用户自界说的布局
值类型的特点:
所有的值类型都直接或间接的派生自 System.ValueType
值类型都是隐式密封的,即不能从其它任何类型担任,也不能派生出任何的类型,目的是防备将值类型用作其它引用类型的基类型。
将值类型赋值给此外一个值类型的变量时,会逐字段进行复制。
每种值类型都有一个默认的结构函数来初始化该类型的默认值。
自界说类型时,什么情况下适合将类型界说为值类型?
类型具有基元类型的特点,即该类型十分简单,没有成员会改削类型的任何实例字段
类型不需要从其它类型担任,亦不派生出任何的类型
类型的实例字段较小(16字节或更小)
类型的实例较大(大于16字节),但不作为要领的实参通报,也不从要领返回。
对付后两点是因为实参默认以传值的方法进行通报,造成对值类型中的字段进行复制,造成性能上的损害。被界说为返回一个值类型的要领返回时,实例中的字段会复制到挪用者的分配的内存中,对性能造成损害。
值类型的装箱和拆箱 装箱:将值类型转换为引用类型的过程称为 装箱(Box).
对值类型实例进行装箱时所产生的工作如下所示:
1. 在托管堆中分配内存。分配的内存量是值类型各字段所需的内存量,还要加上托管堆所有东西都有的两个特别成员(类型东西指针和同步块索引)所需的内处量。
2. 值类型的字段复制到新分配的堆内存中
3. 返回东西的地点。此刻该地点是东西的引用;值类型酿成了引用类型。