原文:[CLR via C#]5.1 基元类型
某些数据类在开发中非常常用,以至于许多编译器允许代码已简化的语法来操作它们。例如可以使用以下语法来分配一个整数:
System.Int32 a = new System.Int32();
当然,你肯定不愿意使用这种语法,C#允许使用如下所示的语法:
int a = 0;
这种语法不仅增强代码的可读性,而且生成的IL代码和是有System.Int32时生成的IL代码是完全一致的。
编译器直接支持的数据类型称为基元类型(primitive type)。基元类型直接映射到Framework类库(FCL)中存在的类型。比如以下4行代码都是正确的,生成的IL代码也是相同的。
int a = 0; System.Int32 a =0; int a = new int(); System.Int32 a = new System.Inte32();
下表列出了FCL类型在C#中对应的基元类型:
C#基元类型 | FCL类型 | CLS相容 | 说明 |
sbyte | System.SByte | NO | 有符号8位值 |
byte | System.Byte | YES | 无符号8位值 |
short | System.Int16 | YES | 有符号16位值 |
ushort | System.UInt16 | NO | 无符号16位值 |
int | System.Int32 | YES | 有符号32位值 |
uint | System.UInt32 | NO | 无符号32位值 |
long | System.Int64 | YES | 有符号64位值 |
ulong | System.UInt64 | NO | 无符号64位值 |
char | System.Char | YES | 16位Unicode字符 |
float | System.Single | YES | IEEE32位浮点值 |
double | System.Double | YES | IEEE64位浮点值 |
bool | System.Boolean | YES | 一个true/false值 |
decimal | System.Decimal | YES | 一个128位高精度浮点值,常用于不容许舍入误差的金融计算 |
string | System.String | YES | 一个字符数组 |
object | System.Object | YES | 所有类型的基类型 |
dynamic | System.Object | YES | 对于CLR,dynamic和object完全一致。然而,C#编译器允许使用一个简单的语法,让dynamic变量参与动态调度 |
可以想象C#编译器自动假定在所有的源代码文件中添加了以下using指令:
using sbyte = System.Sbyte; using byte = System.Byte; using int = System.Int32; using uint = System.UInt32; ......
C#语言规范上说:"从风格上上,最好使用关键字,而不是使用完整的系统类型名称"。但本书作者并不同意这种说法,以下是他的一些理由:
1)很多开发人员困惑于应该使用string还是String。由于C#的string(关键字)是直接映射到System.String(一个FCL类型),所以两者是没有区别的。还有开发人员认为,在32位系统中int是32位整数,在64位系统中就变成64位整数了,事实并不是这样。在C#中,int始终映射到System.Inte32,所有不管什么系统,int都是32位整数,如果都使用Int32就不会产生这种误解。
2)在C#中,long映射到System.Int64,当在其他编程语言中,long可能映射到的是Int16或Int32。这样在看别的编程语言时容易产生误解。
3)FCL的许多方法都将类型名称作为方法名的一部分。
BinaryReader br =new BinaryReader(...); float val = br.ReadSingle(); //正确,当看上去不自然 Single val = br.ReadSingle(); //正确,看上去一目了然
4)平时只用C#的许多程序员逐渐淡忘了还可以使用其他语言写面向CLR的代码。因此造成了"C#主义"入侵类库代码。
对基元类型执行许多算数运算都可能造成溢出。不同语言处理溢出也是不同的。C和C++不将溢出视为错误,并允许值回滚;应用程序"若无其事"的运行着。相反,Microsoft Visual Basic总是将溢出视为错误,并会抛出异常。
CLR提供了一些特殊的IL指令,允许编译器选择它认为最恰当的行为。CLR有一个add指令,将两值相加但不检查溢出。还有一个add.ovf指令,作用是两值相加,溢出时抛出异常。类似的还有sub/sub.ovf等。
C#允许开发人员自己决定如何处理溢出。溢出检查默认是关闭的。开发人员可以使用C#编译器控制溢出的一个办法是使用/checked+编译器开关。
C#通过提供checked和unchecked操作符来实现局部是否检查发生溢出。
unchecked: UInt32 invalid = unchecked((UInt32)(-1)); //OK checked: Byte b = 100; b = checked((Byte)(n+200)); //抛出溢出异常
C#还提供checked和unchecked语句
checked{ Byte b = 100; b = checked((Byte)(n+200)); }
在Visaul Studio的"高级生成设置"对话框中可以指定编译器是否检查溢出。
System.Decimal类型是一个非常特殊的类型。虽然C#将Decimal视为一个基元类型,但CLR则不然,也就是说CLR没有相应的IL指令来决定如何处理Decimal值。Decimal值得处理速度是要慢于其他CLR基元类型的值得处理速度。还有对Decimal来说,checked和uncheked操作符、语句和编译器都是无效的,Decimal溢出时是一定会抛出异常的。