第2章    理解C#类型

C#是一种类型安全的静态语言。类型分三大类:值类型、引用类型、类型参数。

实际上还有第4种类型——指针,但核心C#语言不支持。指针包含数据在内存中的实际位置(地址),还可对指针执行算术运算,就像它们是数字一样。为提供指针的灵活性,C#允许您编写不安全的代码,在这些代码中可创建和操作指针。使用不安全的代码和指针时,务必认识到垃圾收集器不会跟踪指针,您必须复杂分配和释放内存。

值类型又分为结构、枚举类型和可以为null的类型。引用类型又分为类、数组、接口和委托。

 

虽然对类型进行了上述分类,但C#又一个统一类型系统,使得可将任何非指针类型值视为对象。这让值类型获得了引用类型的优点,而不会增加不必要的开销;并可对任何值(包括预定义值类型)调用对象方法。

 

除object和string外,其它所有预定义类型都是值类型。String类型变量是不可修改的。

 

C#中的所有非指针类型都可转换为object,但可能并不是从object派生而来的。

 

C#还有一些特殊的类型,其中最常见的是void。void表示不知道类型。dynamic类型类似于object,主要的不同之处在于,对这种类型执行的所有操作都将在运行阶段(而不是编译阶段)解析。

虽然void和dynamic都是类型,但var是隐式类型,让编译器根据赋给变量的数据确定变量的类型。

 

var并非Variant的缩写

最初引入var类型时,很多人认为它相当于Visual Basic中的Variant类型。Variant变量可用于存储其他任何数据类型的值,因此不属于强类型;而var类型仍是强类型,因为在编译阶段将用特定数据类型替换它。尽管如此,多度使用var可能降低代码的可读性,因此应慎用。

 

全局唯一标识符(GUID)是一个128位的整数值,重复的可能性很小,每当需要唯一标识符时就可以使用它。结构System.Guid让您能够创建和比较GUID值。

 

统一资源标识符(URI)是内联网或Internet上可用资源的简洁表示,可以是绝对URI(如网页地址),也可以是相对URI。Uri类让您能够新建URI以及访问URI的成员,它还提供了处理URI所需的方法,如分析、比较和合并。Uri实例是不能修改的。要创建可修改的URI,可使用UriBuilder。

 

类型System.Nmerics.BigInteger表示任意大的整数值,从理论上说,没有上下限。

 

三目运算符是右结合的,这与其他大部分运算符(左结合的)不同,这意味着对于下面的表达式相当于a?b: (c?d:e):a?b:c?d:e

条件表达式的类型是由consequence和alternative的类型决定的,而不是它被赋给的变量决定的。因此,consequence和alternative的类型必须相同。

 

默认值

对于整型类型,默认值为零。char类型的默认值为空字段,而bool类型的默认值为false。类型object和string的默认值为null,即没有指向任何对象。

 

可以为null的类型是这样一种值类型:可表示其底层类型指定范围内的值以及null值。可以为null的类型用语法Nullable<T>或T?表示,其中T是一种值类型。语法T?使用更广泛。给可以为null的类型的变量赋值时,方法与给其他变量赋值相同:

int = 10;

int? = 10;

int? = null;

要获取可以为null的类型的变量的值,应使用方法GetValueOrDefault,它返回赋给变量的值,如果没有赋值,就返回底层类型的默认值。另外,还可以使用属性HasValue(如果给变量赋值了,该属性将为true)和Value(它返回变量的实际值,如果值为null,就将引发异常)。

所有可以为null的类型(包括引用类型)都支持null合并运算符(??)。将可以为null的类型的变量赋给不能为null的类型的变量时,可使用该运算符指定要返回的默认值。如果该运算符左边的操作数为null,就返回右边的操作数;否则,返回左边的操作数。

 

值类型和引用类型之间的转换通常称为强制转换(cast),因为这种转换使用C# cast运算符,但是相应的CIL指令为box和unbox。

装箱转换总是隐式的,它将值类型转换为引用类型。取消装箱总是显式的,它将装箱的值类型(引用类型)转换为值类型。

装箱和取消装箱操作占用的资源很多,开销也很大,应尽可能避免并确保使用正确的类型来解决问题。

 

隐式转换可能降低精度,但是不应降低量级。以将int值转换为float值为例,它们都是32位的,但是并非每个int值都可以精确地表示为float,这将导致精度降低。然而,由于float的取值范围比int大,因此这种转换不会降低量级。

 

显示转换存在的问题是,如果您不小心,代码就可能能够编译,但是运行时会失败。为降低显示转换在运行阶段失败的可能性,C#提供了as运算符,其形式为e as T,其中e是一个表达式,而T必须是引用类型或可以为null的类型。as运算符告诉编译器,有充分理由相信转换将成功,它试图将值转换为类型T并返回结果,如果转换失败,就返回null。