本文讨论.NET 框架开发人员经常遇到的各种数据类型。熟悉这些类型的不同行为对于一个开发人员来说至关重要。当我刚开始接触.NET框架时,就没有完全理解基元类型、引用类型和值类型之间的一些差别。这种模糊的认识甚至无意间导致了一些难以査找的bug以及性能问题。我希望通过本章的解释,能够帮助大家在提升代码效率的同时避免我曾遇到的一些麻烦。
5.1 基元类型
某些数据类型的使用非常频繁,许多编译器都允许我们用某种简化的语法来操作它们。例如,我们可以用下面的语法来分配一个整数:
System.Int32 a=new system.Int32();
相信大家都会感到用这样的语法来声明和初始化一个整数未免太过麻烦。所幸,许多编译器(包括C#)都允许我们使用类似下面的语法来对整数进行声明和初始化:
int a =0;
这种语法大大提高了代码的可读性,并且经编译后和前一种语法生成的是同样的儿代码。编译器直接支持的数据类型称为基元类型(primitive type)。
基元类型和.NET 框架类库(FCL)中的类型有直接的映射关系。例如,在#中,int直接映射为System.Int32 类型。因此,下面4行代码都能通过编译,并产生同样的I代码:
int a=0; //最便捷的语法
System.Int32 a=0; //较便捷的语法
int a= new int(); //不便捷的语法
System.Int32 a=new System.Int32(); //不便捷的语法
表5.1显示了FCL中的类型和它们在C#中对应的基元类型。对于那些和通用语言规范(CLS)兼容的类型,其他开发语言也都提供了类似的基元类型。但对于那些与CLS不兼容的类型,则无此必要。
表5.1 FCL类型及其在 C#中对应的基元类型
C#中的基元类型 | FCL 类型 | 是否与 CLS 兼容 | 描 述 |
sbyte | System.SByte | 否 | 有符号8位值 |
byte | System.Byte | 是 | 无符号8位值 |
short | System.Intl 6 | 是 | 有符号16位值 |
ushort | System.UInt16 | 否 | 无符号16位值 |
int | System.Int32 | 是 | 有符号32位值 |
uint | System.UInt32 | 否 | 无符号32位值 |
long | System.Int64 | 是 | 有符号64位值 |
ulong | System.UInt64 | 否 | 无符号64位值 |
char | System.Char | 是 | 16 位 Unicode 字符(不像非托管 C++中那样,char表示的是一个8位值) |
float | System.Single | 是 | IEEE32位浮点数 |
double | System.Double | 是 | IEEE 64位浮点数 |
bool | System.Boolean | 是 | 一个 True 或者 Flase值 |
decimal | System.Decimal | 是 | 128位高精度浮点值,通常用于不容许有舍入误差的金融计算场合。在这128位中,1位表示浮点值的符号,96位表示浮点值本身(译注:一个整数值,小数点位置由下面8个位来确定),8位表示用96位值除以浮点值所得结果的10的幂次(幂次范围为0~28)。其余的位尚未使用 |
object | System.Qbiect | 是 | 所有类型的基类型 |
string | System.String | 是 | 一个字符数组 |
C#语言规范声称“作为一种编码风格,使用关键字应该优于使用完整的系统类型名称”。我个人不同意这段论述。我更喜欢使用FCL类型名,并且完全避免使用基元类型名称。实际上,我希望编译器甚至不要提供基元类型名称,并强制开发人员使用FCL类型名。理由如下:
- 我发现很多开发人员都困惑于不知在代码中使用string还是 String。因为 C#中的 string(关键字)实际上映射了 System.String(FCL 类型),所以两者之间没有任何不同,都可以使用。
- 在C#中,long 映射为 System.Int64,但是在其他的编程语言中,long 可能映射为一个 Int16或者 Int32。实际上,托管扩展 C++将 1ong看作是一个Int32。如果习惯了在一种语言下编程,再转而去阅读用另一种语言编写的代码就很容易误解其中的意图。实际上,大多数语言甚至不将 long 看作是关键字,也不会编译使用它的代码。
- FCL,中有很多类型的方法都将一些类型名作为方法名称的一部分。例如,BinaryReader 类型就提供了诸如 ReadBoolean、ReadInt32、ReadSingle 之类的方法;而 System.Convert 类型也提供了诸如 ToBoolean、ToInt32、ToSingle 之类的方法。虽然下面的代码是合法的,但其中含有float的代码行在我看来总有些不自然,这段代码的正确性也并不明显:
BinaryReader br=new BinaryReader(...);
float val=br.ReadSingle(); //正确,但是有些不自然
Single val= br.Readsingle(); //正确,并且感觉也很自然
因为上面所有这些原因,本书将通篇采用FCL类型名。
在许多编程语言中,我们可能希望下面的代码能够正确地编译并执行:
Int32 i= 5; //一个32位的值
Int64 l=i; //隐式转型为一个64位的值
大家可能会认为这段代码不能通过编译。毕竟,System.Int32和 System.Int64是不同的类型。但是,我们可能会很高兴看到C#编译器能够正确地编译这段代码,并且能够按我们的预期运行。为什么呢?
这是因为 C#编译器熟悉基元类型,并且在编译代码时会应用自己的规则。换句话说,我们所选的编译器能够识别一些通用的编程模式,并产生必要的几,指令来使代码按期望的方式运行。
具体而言,编译器一般会支持和类型转换、文本常量(1iterals)、操作符相关的一些模式。看下面的例子。
首先,编译器能够在基元类型之间进行隐式或者显式的转型: