程序员人生 网站导航

C#中的数据类型

栏目:php教程时间:2016-06-22 15:46:56

C# 语言的类型划分为两大类:值类型 (Value type) 和援用类型 (reference type)。值类型和援用类型都可以为泛型类型 (generic type),泛型类型采取1个或多个类型参数。类型参数可以指定值类型和援用类型。

type:
value-type
reference-type
type-parameter

第3种类型是指针,只能用在不安全代码中。第 18.2 节对此做了进1步的探讨。

值类型与援用类型的不同的地方在于:值类型的变量直接包括其数据,而援用类型的变量存储对其数据的援用 (reference),后者称为对象 (object)。对援用类型,两个变量可能援用同1个对象,因此对1个变量的操作可能影响另外一个变量所援用的对象。对值类型,每一个变量都有自己的数据副本,对1个变量的操作不可能影响另外一个变量。

C# 的类型系统是统1的,因此任何类型的值都可以按对象处理。C# 中的每一个类型直接或间接地从 object 类类型派生,而 object 是所有类型的终究基类。援用类型的值都被视为 object 类型,被简单地当作对象来处理。值类型的值则通过对其履行装箱和拆箱操作(第 4.3 节)按对象处理。

1.1 值类型

1个值类型或是结构类型,或是枚举类型。C# 提供称为简单类型 (simple type) 的预定义结构类型集。简单类型通过保存字标识。

value-type:
struct-type
enum-type

struct-type:
type-name
simple-type
nullable-type

simple-type:
numeric-type
bool

numeric-type:
integral-type
floating-point-type
decimal

integral-type:
sbyte
byte
short
ushort
int
uint
long
ulong
char

floating-point-type:
float
double

nullable-type:
non-nullable-value-type   ?

non-nullable-value-type:
type

enum-type:
type-name

与援用类型的变量不同的是,仅当该值类型是可以为 null 的类型时,值类型的变量才可包括 null 值。 对每一个不可以为 null 的值类型,都存在1个对应的可以为 null 的值类型,该类型表示相同的值集加上 null 值。

对值类型变量赋值时,会创建所赋的值的1个副本。这不同于援用类型的变量赋值,援用类型的变量赋值复制的是援用而不是由援用标识的对象。

1.1.1 System.ValueType 类型

所有值类型从类 System.ValueType 隐式继承,后者又从类 object 继承。任何类型都不可能从值类型派生,因此,所有值类型都是隐式密封的(第 10.1.1.2 节)。

注意,System.ValueType 本身不是 value-type, 而是 class-type,所有 value-type 都从它自动派生。

1.1.2 默许构造函数

所有值类型都隐式声明1个称为默许构造函数 (default constructor) 的公共无参数实例构造函数。默许构造函数返回1个零初始化实例,它就是该值类型的默许值 (default value):

  • 对所有 simple-types,默许值是由所有位都置零的位模式产生的值:
  • 对 sbyte、byte、byte、ushort、int、uint、long 和 ulong,默许值为 0。
  • 对 char,默许值为 '\x0000'。
  • 对 float,默许值为 0.0f。
  • 对 double,默许值为 0.0d。
  • 对 decimal,默许值为 0.0m。
  • 对 bool,默许值为 false。
    • 对 enum-type E,默许值为 0,该值被转换为类型 E。
    • 对 struct-type,默许值是通过将所有值类型字段设置为它们的默许值并将所有援用类型字段设置为 null 而产生的值。
    • 对 nullable-type,默许值是1个其 HasValue 属性为 false 且 Value 属性未定义的实例。默许值也称为可以为 null 的类型的 null (null value)。
    • 与任何其他实例构造函数1样,值类型的默许构造函数也是用 new 运算符调用的。出于效力缘由,实际上,没必要故意调用它的构造函数。在下面的示例中,变量 i 和 j 都被初始化为零。

class A
{
void F() {
     int i = 0;
     int j = new int();
}
}

由于每一个值类型都隐式地具有1个公共无形参实例构造函数,因此,1个结构类型中不可能包括1个关于无形参构造函数的显式声明。但允许结构类型声明参数化实例构造函数(第 11.3.8 节)。

1.1.3 结构类型

结构类型是1种值类型,它可以声明常量、字段、方法、属性、索引器、运算符、实例构造函数、静态构造函数和嵌套类型。结构类型的声明在第 11.1 节中说明。

1.1.4 简单类型

C# 提供称为简单类型 (simple type) 的预定义结构类型集。简单类型通过保存字标识,而这些保存字只是 System 命名空间中预定义结构类型的别名,详见下表。


..

化名的类型

sbyte

System.SByte

byte

System.Byte

short

System.Int16

ushort

System.UInt16

int

System.Int32

uint

System.UInt32

long

System.Int64

ulong

System.UInt64

char

System.Char

float

System.Single

double

System.Double

bool

System.Boolean

decimal

System.Decimal


由于简单类型是结构类型的别名,所以每一个简单类型都具有成员。例如,int 具有在 System.Int32 中声明的成员和从 System.Object 继承的成员,允许使用下面的语句:

int i = int.MaxValue;        // System.Int32.MaxValue constant
string s = i.ToString();     // System.Int32.ToString() instance method
string t = 123.ToString();       // System.Int32.ToString() instance method

简单类型与其他结构类型的不同的地方在于,简单类型允许某些附加的操作:

  • 大多数简单类型允许通过编写 literals(第 2.4.4 节)来创建值。例如,123 是类型 int 的文本,'a' 是类型 char 的文本。C# 没有普遍地为结构类型设置类似的以文本创建值的规则,所以其他结构类型的非默许值终究总是通过这些结构类型的实例构造函数来创建的。
  • 当表达式的操作数都是简单类型常量时,编译器可以在编译时计算表达式。这样的表达式称为 constant-expression(第 7.19 节)。触及其他结构类型所定义的运算符的表达式不被视为常量表达式。
  • 通过 const 声明可以声明简单类型(第 10.4 节)的常量。常量不可能属于其他结构类型,但 static readonly 字段提供了类似的效果。
  • 触及简单类型的转换可以参与由其他结构类型定义的转换运算符的计算,但用户定义的转换运算符永久不能参与其他用户定义运算符的计算(第 6.4.3 节)。

1.1.5 整型

  • C# 支持 9 种整型:sbyte、byte、short、ushort、int、uint、long、ulong 和 char。整型具有以下所列的大小和取值范围:
  • sbyte 类型表示有符号 8 位整数,其值介于 ⑴28 和 127 之间。
  • byte 类型表示无符号 8 位整数,其值介于 0 和 255 之间。
  • short 类型表示有符号 16 位整数,其值介于 ⑶2768 和 32767 之间。
  • ushort 类型表示无符号 16 位整数,其值介于 0 和 65535 之间。
  • int 类型表示有符号 32 位整数,其值介于 ⑵147483648 和 2147483647 之间。
  • uint 类型表示无符号 32 位整数,其值介于 0 和 4294967295 之间。
  • long 类型表示有符号 64 位整数,其值介于 ⑼223372036854775808 和 9223372036854775807 之间。
  • ulong 类型表示无符号 64 位整数,其值介于 0 和 18446744073709551615 之间。
  • char 类型表示无符号 16 位整数,其值介于 0 和 65535 之间。char 类型的可能值集与 Unicode 字符集相对应。虽然 char 的表示情势与 ushort 相同,但是可以对1种类型进行的所有计算并不是都可以对另外一种类型履行。

整型1元运算符和2元运算符总是对有符号 32 位精度、无符号的 32 位精度、有符号 64 位精度或无符号 64 位精度进行计算:

  • 对1元运算符 + 和 ~,操作数转换为 T 类型,其中 T 是 int、uint、long 和 ulong 中第1个可以完全表示操作数的所有可能值的类型。然后用 T 类型的精度履行运算,结果的类型是 T 类型。
  • 对1元运算符 –,操作数转换为类型 T,其中 T 是 int 和 long 中第1个可以完全表示操作数的所有可能值的类型。然后用 T 类型的精度履行运算,结果的类型是 T 类型。1元运算符 – 不能利用于类型 ulong 的操作数。
  • 对 +、–、*、/、%、&、^、|、==、!=、>、<、>= 和 <= 2元运算符,操作数转换为类型 T,其中 T 是 int、uint、long 和 ulong 中第1个可以完全表示两个操作数的所有可能值的类型。然后用 T 类型的精度履行运算,运算的结果的类型也属于 T(对关系运算符为 bool)。对2元运算符,不允许1个操作数为 long 类型而另外一个操作数为 ulong 类型。
  • 对2元运算符 << 和 >>,左操作数转换为 T 类型,其中 T 是 int、uint、long 和 ulong 中第1个可以完全表示操作数的所有可能值的类型。然后用 T 类型的精度履行运算,结果的类型是 T 类型。

char 类型归类为整型类型,但它在以下两个方面不同于其他整型:

  • 不存在从其他类型到 char 类型的隐式转换。具体而言,即便 sbyte、byte 和 ushort 类型具有完全可以用 char 类型来表示的值范围,也不存在从 sbyte、byte 或 ushort 到 char 的隐式转换。
  • char 类型的常量必须写成 character-literal 或带有强迫转换为类型 char 的 integer-literal。例如,(char)10 与 '\x000A' 是相同的。

checked 和 unchecked 运算符和语句用于控制整型算术运算和转换(第 7.6.12 节)的溢出检查。在 checked 上下文中,溢生产生编译时毛病或致使引发 System.OverflowException。在 unchecked 上下文中将疏忽溢出,任何与目标类型不匹配的高序位都被放弃。

1.1.6 浮点型

C# 支持两种浮点型:float 和 double。float 和 double 类型用 32 位单精度和 64 位双精度 IEEE 754 格式来表示,这些格式提供以下几组值:

  • 正零和负零。大多数情况下,正零和负零的行动与简单的值零相同,但某些运算会区分对待此两种零(第 7.8.2 节)。
  • 正无穷大和负无穷大。无穷大是由非零数字被零除这样的运算产生的。例如,1.0 / 0.0 产生正无穷大,而 –1.0 / 0.0 产生负无穷大。
  • 非数字 (Not-a-Number) 值,常缩写为 NaN。NaN 是由无效的浮点运算(如零被零除)产生的。
  • 以 s × m × 2e 情势表示的非零值的有限集,其中 s 为 1 或 −1,m 和 e 由特殊的浮点类型肯定:对 float,为 0 < m < 224 并且 −149 ≤ e ≤ 104;对 double,为 0 < m < 253 并且 −1075 ≤ e ≤ 970。非标准化的浮点数被视为有效非零值。

float 类型可表示精度为 7 位、在大约 1.5 × 10−45 到 3.4 × 1038 的范围内的值。

double 类型可表示精度为 15 位或 16 位、在大约 5.0 × 10−324 到 1.7 × 10308 的范围内的值。

如果2元运算符的1个操作数为浮点型,则另外一个操作数必须为整型或浮点型,并且运算按下面这样计算:

  • 如果1个操作数为整型,则该操作数转换为与另外一个操作数的类型相同的浮点型。
    • 然后,如果任1操作数的类型为 double,则另外一个操作数转换为 double。最少用 double 范围和精度履行运算,结果的类型为 double(对关系运算符则为 bool)。
    • 否则,最少用 float 范围和精度履行运算,结果的类型为 float(对关系运算符则为 bool)。

浮点运算符(包括赋值运算符)历来不产生异常。相反,在异常情况下,浮点运算产生零、无穷大或 NaN,以下所述:

  • 如果浮点运算的结果对目标格式太小,则运算结果变成正零或负零。
  • 如果浮点运算的结果对目标格式太大,则运算结果变成正无穷大或负无穷大。
    • 如果浮点运算无效,则运算的结果变成 NaN。
    • 如果浮点运算的1个或两个操作数为 NaN,则运算的结果变成 NaN。

可以用比运算的结果类型更高的精度来履行浮点运算。例如,某些硬件结构支持比 double 类型具有更大的范围和精度的“extended”或“long double”浮点型,并隐式地使用这类更高精度类型履行所有浮点运算。只有性能开消过大,才能使这样的硬件结构用“较低”的精度履行浮点运算。C# 采取的是允许将更高的精度类型用于所有浮点运算,而不是强求履行规定的精度,造成同时损失性能和精度。除传递更精确的结果外,这样做很少会产生任何可发觉的效果。但是,在 x * y / z 情势的表达式中,如果其中的乘法会产生超越 double 范围的结果,而后面的除法使临时结果返回到 double 范围内,则以更大范围的格式去计算该表达式,可能会产生有限值的结果(本来应是无穷大)。

1.1.7 decimal 类型

decimal 类型是 128 位的数据类型,合适用于财务计算和货币计算。decimal 类型可以表示具有 28 或 29 个有效数字、从 1.0 × 1028 到大约 7.9 × 1028 范围内的值。

decimal 类型的有限值集的情势为 (–1)s × c × 10-e,其中符号 s 是 0 或 1,系数 c 由 0 ≤ c < 296 给定,小数位数 e 满足 0 ≤ e ≤ 28。decimal 类型不支持有符号的零、无穷大或 NaN。decimal 可用1个以 10 的幂表示的 96 位整数来表示。对绝对值小于 1.0m 的 decimal,它的值最多精确到第 28 位小数。对绝对值大于或等于 1.0m 的 decimal,它的值精确到小数点后第 28 或 29 位。与 float 和 double 数据类型相反,10进制小数数字(如 0.1)可以精确地用 decimal 表示情势来表示。在 float 和 double 表示情势中,这类数字通常变成无穷小数,使这些表示情势更容易产生舍入毛病。

如果2元运算符的1个操作数为 decimal 类型,则另外一个操作数必须为整型或 decimal 类型。如果存在1个整型操作数,它将在履行运算前转换为 decimal。

decimal 类型值的运算结果是这样得出的:先计算1个精确结果(按每一个运算符的定义保存小数位数),然后舍入以合适表示情势。结果舍入到最接近的可表示值,当结果一样地接近于两个可表示值时,舍入到最小有效位数位置中为偶数的值(这称为“银行家舍入法”)。零结果总是包括符号 0 和小数位数 0。

如果10进制算术运算产生1个绝对值小于或等于 5 × 10⑵9 的值,则运算结果变成零。如果 decimal 算术运算产生的值对 decimal 格式太大,则将引发 System.OverflowException。

与浮点型相比,decimal 类型具有较高的精度,但取值范围较小。因此,从浮点型到 decimal 的转换可能会产生溢出异常,而从 decimal 到浮点型的转换则可能致使精度损失。由于这些缘由,在浮点型和 decimal 之间不存在隐式转换,如果没有显式地标出强迫转换,就不可能在同1表达式中同时使用浮点操作数和 decimal 操作数。

1.1.8 bool 类型

bool 类型表示布尔逻辑量。bool 类型的可能值为 true 和 false。

在 bool 和其他类型之间不存在标准转换。具体而言,bool 类型与整型截然不同,不能用 bool 值代替整数值,反之亦然。

在 C 和 C++ 语言中,零整数或浮点值或 null 指针可以转换为布尔值 false,非零整数或浮点值或非 null 指针可以转换为布尔值 true。在 C# 中,这类转换是通过显式地将整数或浮点值与零进行比较,或显式地将对象援用与 null 进行比较来完成的。

1.1.9 枚举类型

枚举类型是具有命名常量的独特的类型。每一个枚举类型都有1个基础类型,该基础类型必须为 byte、sbyte、short、ushort、int、uint、long 或 ulong。枚举类型的值集和它的基础类型的值集相同。枚举类型的值其实不只限于那些命名常量的值。枚举类型是通过枚举声明(第 14.1 节)定义的。

1.1.10 可以为 null 的类型

可以为 null 的类型可以表示其基础类型 (underlying type) 的所有值和1个额外的 null 值。可以为 null 的类型写作 T?,其中 T 是基础类型。此语法是 System.Nullable的简写情势,这两种情势可以互换使用。

相反,不可以为 null 的值类型 (non-nullable value type) 可以是除 System.Nullable及其简写情势T?(对任何类型的 T)以外的任何值类型,加上束缚为不可以为 null 的值类型的任何类型参数(即具有 struct 束缚的任何类型参数)。System.Nullable类型指定 T 的值类型束缚(第 10.1.5 节),这意味着可以为 null 的类型的基础类型可以是任何不可以为 null 的值类型。可以为 null 的类型的基础类型不能是可以为 null 的类型或援用类型。例如,int?? 和 string? 是无效类型。

可以为 null 的类型 T? 的实例有两个公共只读属性:

  • 类型为 bool 的 HasValue 属性
  • 类型为 T 的 Value 属性

HasValue 为 true 的实例称为非 null。非 null 实例包括1个已知值,可通过 Value 返回该值。

HasValue 为 false 的实例称为 null。null 实例有1个不肯定的值。尝试读取 null 实例的 Value 将致使引发 System.InvalidOperationException。访问可以为 null 的实例的 Value 属性的进程称作解包 (unwrapping)。

除默许构造函数以外,每一个可以为 null 的类型 T? 都有1个具有类型为 T 的单个实参的公共构造函数。例如,给定1个类型为 T 的值 x,调用形如

new T?(x)

的构造函数将创建 T? 的非 null 实例,其 Value 属性为 x。为1个给定值创建可以为 null 的类型的非 null 实例的进程称作包装 (wrapping)。

从 null 文本转换为 T?(第 6.1.5 节)和从 T 转换为 T?(第 6.1.4 节)可以使用隐式转换。

1.2 援用类型

援用类型是类类型、接口类型、数组类型或拜托类型。

reference-type:
class-type
interface-type
array-type
delegate-type

class-type:
type-name
object
dynamic
string

interface-type:
type-name

array-type:
non-array-type   rank-specifiers

non-array-type:
type

rank-specifiers:
rank-specifier
rank-specifiers   rank-specifier

rank-specifier:
[   dim-separatorsopt   ]

 dim-separators:
,
dim-separators   ,

delegate-type:
type-name

援用类型值是对该类型的某个实例 (instance) 的1个援用,后者称为对象 (object)。null 值比较特别,它兼容于所有援用类型,用来表示“没有被援用的实例”。

1.2.1 类类型

类类型定义包括数据成员、函数成员和嵌套类型的数据结构,其中数据成员包括常量和字段,函数成员包括方法、属性、事件、索引器、运算符、实例构造函数、析构函数和静态构造函数。类类型支持继承,继承是派生类可用来扩大和专门化基类的1种机制。类类型的实例是用 object-creation-expressions(第 7.6.10.1 节)创建的。

有关类类型的介绍详见第 10 章。

某些预定义类类型在 C# 语言中有特殊含义,以下表所示。


类类型

说明

System.Object

所有其他类型的终究基类。请参见第 4.2.2 节。

System.String

C# 语言的字符串类型。请参见第 4.2.4 节。

System.ValueType

所有值类型的基类。请参见第 4.1.1 节。

System.Enum

所有枚举类型的基类。请参见第 14 章。

System.Array

所有数组类型的基类。请参见第 12 章。

System.Delegate

所有拜托类型的基类。请参见第 15 章。

System.Exception

所有异常类型的基类。请参见第 16 章。


1.2.2 对象类型

object 类类型是所有其他类型的终究基类。C# 中的每种类型都是直接或间接从 object 类类型派生的。

关键字 object 只是预定义类 System.Object 的别名。

1.2.3 dynamic 类型

dynamic 类型与 object 1样,可以援用任何对象。在将运算符利用于 dynamic 类型的表达式时,其解析会推延到程序运行时进行。因此,如果运算符不能合法地利用于援用的对象,在编译进程中不会报告任何毛病。而是在运行时解析运算符失败时,会引发异常。

在第 4.7 节中进1步介绍了动态类型,在第 7.2.2 节中进1步介绍了动态绑定。

1.2.4 string 类型

string 类型是直接从 object 继承的密封类类型。string 类的实例表示 Unicode 字符串。

string 类型的值可以写为字符串(第 2.4.4.5 节)。

关键字 string 只是预定义类 System.String 的别名。

1.2.5 接口类型

1个接口定义1个协议。实现某接口的类或结构必须遵照该接口定义的协议。1个接口可以从多个基接口继承,而1个类或结构可以实现多个接口。

有关接口类型的介绍详见第 13 章。

1.2.6 数组类型

数组是1种数据结构,它包括可通过计算索引访问的零个或更多个变量。数组中包括的变量(又称数组的元素)具有相同的类型,该类型称为数组的元素类型。

有关数组类型的介绍详见第 12 章。

1.2.7 拜托类型

拜托是援用1个或多个方法的数据结构。对实例方法,拜托还可援用实例方法对应的对象实例。

在 C 或 C++ 中与拜托最接近的是函数指针,但函数指针只能援用静态函数,而拜托则既可以援用静态方法,也能够援用实例方法。在后1种情况中,拜托不但存储了1个对该方法入口点的援用,还存储了1个对相应的对象实例的援用,该方法就是通过此对象实例被调用的。

有关拜托类型的介绍详见第 15 章。

1.3 装箱和拆箱

装箱和拆箱的概念是 C# 的类型系统的核心。它在 value-typesreference-types 之间架起了1座桥梁,使得任何 value-type 的值都可以转换为 object 类型的值,反过来转换也能够。装箱和拆箱使我们能够统1地来考察类型系统,其中任何类型的值终究都可以按对象处理。

1.3.1 装箱转换

装箱转换允许将 value-type 隐式转换为 reference-type。存在以下装箱转换:

  • 从任何 value-type 到 object 类型。
  • 从任何 value-type 到 System.ValueType 类型。
  • 从任何 non-nullable-value-type 到 value-type 实现的任何 interface-type。
  • 从任何 nullable-type 到由 nullable-type 的基础类型实现的任何 interface-type
  • 从任何 enum-type 到 System.Enum 类型。
  • 从任何具有基础 enum-type 的 nullable-type 到 System.Enum 类型。

请注意,对类型形参进行隐式转换将以装箱转换的情势履行(如果在运行时它最后从值类型转换到援用类型(第 6.1.10 节))。

non-nullable-value-type 的1个值装箱包括以下操作:分配1个对象实例,然后将 non-nullable-value-type 的值复制到该实例中。

nullable-type 的值装箱时,如果该值为 null 值(HasValue 为 false),将产生1个 null 援用;否则将产生对基础值解包和装箱的结果。

最能说明 non-nullable-value-type 的值的实际装箱进程的办法是,假想有1个泛型装箱类 (boxing class),其行动与下面声明的类相似:

sealed class Box: System.ValueType
{
T value;

public Box(T t) {
     value = t;
}
}

T 类型值 v 的装箱进程现在包括履行表达式 new Box(v) 和将结果实例作为 object 类型的值返回。因此,下面的语句

int i = 123;
object box = i;

在概念上相当于

int i = 123;
object box = new Box(i);

实际上,像上面这样的 Box装箱类其实不存在,并且装箱值的动态类型也不会真的属于1个类类型。相反,T 类型的装箱值属于动态类型 T,若用 is 运算符来检查动态类型,也仅能援用类型 T。例如,

int i = 123;
object box = i;
if (box is int) {
Console.Write("Box contains an int");
}

将在控制台上输出字符串“Box contains an int”。

装箱转换隐含着复制1份 待装箱的值。这不同于从 reference-type 到 object 类型的转换,在后1种转换中,转换后的值继续援用同1实例,只是将它当作派生程度较小的 object 类型而已。例如,给定下面的声明

struct Point
{
public int x, y;

public Point(int x, int y) {
     this.x = x;
     this.y = y;
}
}

则下面的语句

Point p = new Point(10, 10);
object box = p;
p.x = 20;
Console.Write(((Point)box).x);

将在控制台上输出值 10,由于将 p 赋值给 box 是1个隐式装箱操作,它将复制 p 的值。如果将 Point 声明为 class,由于 p 和 box 将援用同1个实例,因此输出值为 20。

1.3.2 拆箱转换

取消装箱转换允许将 reference-type 显式转换为 value-type。存在以下拆箱转换:

  • 从 object 类型到任何 value-type。
  • 从 System.ValueType 类型到任何 value-type。
  • 从任何 interface-type 到实现了该 interface-type 的任何 non-nullable-value-type
  • 从任何 interface-type 到其基础类型实现了该 interface-type 的任何 nullable-type
  • 从 System.Enum 类型到任何 enum-type。
  • 从 System.Enum 类型到任何具有基础 enum-type 的 nullable-type。

请注意,到类型形参的显式转换将以取消装箱转换的情势履行(如果在运行时它结束从援用类型到值类型(第 6.2.6 节)的转换)。

non-nullable-value-type 取消装箱的操作包括以下步骤:首先检查对象实例是不是是给定 non-nullable-value-type 的装箱值,然后将该值从实例中复制出来。

nullable-type 取消装箱在源操作数为 null 时会产生 nullable-type 的 null 值;否则将产生从对象实例到 nullable-type 的基础类型的取消装箱的包装结果。

参照前1节中关于假想的装箱类的描写,从对象 box 到 value-type T 的取消装箱转换包括履行表达式 ((Box)box).value。因此,下面的语句

object box = 123;
int i = (int)box;

在概念上相当于

object box = new Box(123);
int i = ((Box)box).value;

为使针对给定 non-nullable-value-type 的取消装箱转换在运行时获得成功,源操作数的值必须是对该 non-nullable-value-type 的装箱值的援用。如果源操作数为 null,则将引发 System.NullReferenceException。如果源操作数是对不兼容对象的援用,则将引发 System.InvalidCastException。

为使针对给定 nullable-type 的取消装箱转换在运行时获得成功,源操作数的值必须是 null 或是对该 nullable-type 的基础 non-nullable-value-type 的装箱值的援用。如果源操作数是对不兼容对象的援用,则将引发 System.InvalidCastException。

1.4 构造类型

泛型类型声明本身表示未绑定的泛型类型 (unbound generic type),它通过利用类型实参 (type argument) 被用作构成许多不同类型的“蓝图”。类型实参编写在紧跟在泛型类型的名称后面的尖括号(< 和 >)中。最少包括1个类型实参的类型称为构造类型 (constructed type)。构造类型可以在语言中能够出现类型名的大多数地方使用。未绑定的泛型类型只能在 typeof-expression(第 7.6.11 节)中使用。

构造类型还可以在表达式中用作简单名称(第 7.6.2 节)或在访问成员时使用(第 7.6.4 节)。

在计算 namespace-or-type-name 时,仅斟酌具有正确数目的类型形参的泛型类型。因此,可使用同1个标识符标识不同的类型,条件是那些类型具有不同数目的类型形参。当在同1程序中混合使用泛型和非泛型类时,这是很有用的:

namespace Widgets
{
class Queue {...}
class Queue{...}
}

namespace MyApplication
{
using Widgets;

class X
{
     Queue q1;         // Non-generic Widgets.Queue
     Queueq2;       // Generic Widgets.Queue
}
}

即便未直接指定类型形参,type-name 也能够标识构造类型。当某个类型嵌套在泛型类声明中,并且包括该类型的声明的实例类型被隐式用于名称查找(第 10.3.8.6 节)时,就会出现这类情况:

class Outer
{
public class Inner {...}

public Inner i;             // Type of i is Outer.Inner
}

在不安全代码中,构造类型不能用作 unmanaged-type(第 18.2 节)。

1.4.1 类型实参

类型实参列表中的每一个实参都只是1个 type。

type-argument-list:
< type-arguments >

type-arguments:
type-argument
type-arguments   ,   type-argument

type-argument:
type

在不安全代码(第 18 章)中,type-argument 不可以是指针类型。每一个类型实参都必须满足对应的类型形参上的所有束缚(第 10.1.5 节)。

1.4.2 开放和封闭类型

所有类型都可归类为开放类型 (open type) 或封闭类型 (closed type)。开放类型是包括类型形参的类型。更明确地说:

  • 类型形参定义开放类型。
  • 当且仅当数组元素类型是开放类型时,该数组类型才是开放类型。
  • 当且仅当构造类型的1个或多个类型实参为开放类型时,该构造类型才是开放类型。当且仅当构造的嵌套类型的1个或多个类型实参或其包括类型的类型实参为开放类型时,该构造的嵌套类型才是开放类型。

封闭类型是不属于开放类型的类型。

在运行时,泛型类型声明中的所有代码都在1个封闭构造类型的上下文中履行,这个封闭构造类型是通过将类型实参利用该泛型声明来创建的。泛型类型中的每一个类型形参都绑定到特定的运行时类型。所有语句和表达式的运行时处理都始终使用封闭类型,开放类型仅出现在编译时处理进程中。

每一个封闭构造类型都有自己的静态变量集,任何其他封闭构造类型都不会同享这些变量。由于开放类型在运行时其实不存在,因此不存在与开放类型关联的静态变量。如果两个封闭构造类型是从相同的未绑定泛型类型构造的,并且它们的对应类型实参属于相同类型,则这两个封闭构造类型是相同类型。

1.4.3 绑定和未绑定类型

术语未绑定类型 (unbound type) 是指非泛型类型或未绑定的泛型类型。术语绑定类型 (bound type) 是指非泛型类型或构造类型。

未绑定类型是指类型声明所声明的实体。未绑定泛型类型本身不是1种类型,不能用作变量、参数或返回值的类型,也不能用作基类型。可以援用未绑定泛型类型的唯1构造是 typeof 表达式(第 7.6.11 节)。

1.4.4 满足束缚

每当援用构造类型或泛型方法时,都会根据泛型类型或方法(第 10.1.5 节)上声明的类型形参束缚对所提供的类型实参进行检查。对每一个 where 子句,将根据每一个束缚检查与命名的类型形参相对应的类型实参 A,以下所示:

  • 如果束缚为类类型、接口类型或类型形参,则假定 C 表示该束缚,并用所提供的类型实参替换出现在该束缚中的任何类型形参。若要满足该束缚,必须可通过以下方式之1将类型 A 转换为类型 C:
  • 标识转换(第 6.1.1 节)
  • 隐式援用转换(第 6.1.6 节)
  • 装箱转换(第 6.1.7 节)— 条件是类型 A 为不可以为 null 的值类型。
  • 从类型形参 A 到 C 的隐式援用、装箱或类型形参转换。
    • 如果束缚为援用类型束缚 (class) 则类型 A 必须满足以下条件之1:
    • A 为接口类型、类类型、拜托类型或数组类型。注意,System.ValueType 和 System.Enum 是满足此束缚的援用类型。
    • A 是已知为援用类型的类型形参(第 10.1.5 节)。
      • 如果束缚为值类型束缚 (struct) 则类型 A 必须满足以下条件之1:
      • A 为结构类型或枚举类型,但不是可以为 null 的类型。请注意,System.ValueType 和 System.Enum 是不满足此束缚的援用类型。
      • A 为具有值类型束缚的类型形参(第 10.1.5 节)。
        • 如果束缚为构造函数束缚 new(),则类型 A 1定不能为 abstract,并且必须具有公共无形参构造函数。如果以下条件之1成立,则满足此条件:
        • A 为值类型,由于所有值类型都具有公共默许构造函数(第 4.1.2 节)。
        • A 为具有构造函数束缚的类型形参(第 10.1.5 节)。
        • A 为具有值类型束缚的类型形参(第 10.1.5 节)。
        • A 是不为 abstract 并且包括显式声明的无参数 public 构造函数的类。
        • A 不为 abstract,并且具有默许构造函数(第 10.11.4 节)。

如果给定的类型实参未满足1个或多个类型形参的束缚,则会产生编译时毛病。

由于类型形参未被继承,因此束缚也从不被继承。在下面的示例中,T 需要指定其类型形参 T 上的束缚,以便 T 满足基类 B所施加的束缚。相反,类 E 不需要指定束缚,由于对任何 T,List都实现 IEnumerable。

class Bwhere T: IEnumerable {...}

class D: Bwhere T: IEnumerable {...}

class E: B{...}

1.5 类型形参

类型形参是指定形参在运行时要绑定到的值类型或援用类型的标识符。

type-parameter:
identifier

由于类型形参可以使用许多不同的实际类型实参进行实例化,因此类型形参具有与其他类型略微不同的操作和限制。这包括:

  • 不能直接使用类型形参声明基类(第 10.2.4 节)或接口(第 13.1.3 节)。
  • 类型形参上的成员查找规则取决于利用到该类型形参的束缚(如果有)。这将在第 7.4 节中详细描写。
  • 类型形参的可用转换取决于利用到该类型形参的束缚(如果有)。这将在第 6.1.10 和 6.2.6 节中详细描写。
  • 如果事前不知道由类型形参给出的类型是援用类型(第 6.1.10 节),不能将标识 null 转换为该类型。不过,可以改成使用 default 表达式(第 7.6.13 节)。另外,具有由类型形参给出的类型的值可以 使用 == 和 != 与 null 进行比较(第 7.10.6 节),除非该类型形参具有值类型束缚。
  • 仅当类型形参受 constructor-constraint 或值类型束缚(第 7.6.10.1 节)的束缚时,才能将 new 表达式(第 10.1.5 节)与类型形参联合使用。
  • 不能在特性中的任何位置上使用类型形参。
    • 不能在成员访问(第 7.6.4 节)或类型名称(第 3.8 节)中使用类型形参标识静态成员或嵌套类型。
    • 在不安全代码中,类型形参不能用作 unmanaged-type(第 18.2 节)。

作为类型,类型形参纯洁是1个编译时构造。在运行时,每一个类型形参都绑定到1个运行时类型,运行时类型是通过向泛型类型声明提供类型实参来指定的。因此,使用类型形参声明的变量的类型在运行时将是封闭构造类型(第 4.4.2 节)。触及类型形参的所有语句和表达式的运行时履行都使用作为该形参的类型实参提供的实际类型。

1.6 表达式树类型

表达式树 (Expression tree) 允许匿名函数表示为数据结构而不是可履行代码。表达式树是 System.Linq.Expressions.Expression情势的表达式树类型 (expression tree type) 的值,其中 D 是任何拜托类型。对本规范的其余部份,我们将使用简写情势 Expression援用这些类型。

如果存在从匿名函数到拜托类型 D 的转换,则也存在到表达式树类型 Expression的转换。不过,匿名函数到拜托类型的转换会生成1个援用该匿名函数的可履行代码的拜托,而到表达式树类型的转换则会创建该匿名函数的表达式树表示情势。

表达式树是匿名函数有效的内存数据表示情势,它使匿名函数的结构变得透明和明晰。

与拜托类型 D 1样,Expression具有与 D 相同的参数和返回类型。

下面的示例将匿名函数表示为可履行代码和表达式树。由于存在到 Func 的转换,所以也存在到 Expression> 的转换:

Func del = x => x + 1;                   // Code

Expression> exp = x => x + 1;       // Data

进行上面的赋值以后,拜托 del 援用返回 x + 1 的方法,表达式目录树 exp 援用描写表达式 x => x + 1 的数据结构。

泛型类型 Expression的准肯定义和当将匿名函数转换为表达式树类型时用于构造表达式树的确切规则不在本规范的范围以内,将另作说明。

有两个要点需要明确指出:

  • 并不是所有匿名函数都能表示为表达式树。例如,具有语句体的匿名函数和包括赋值表达式的匿名函数就不能表示为表达式树。在这些情况下,转换仍存在,但在编译时将失败。
  • Expression提供1个实例方法 Compile,该方法产生1个类型为 D 的拜托:

Func del2 = exp.Compile();

调用此拜托将致使履行表达式树所表示的代码。因此,根据上面的定义,del 和 del2 等效,而且下面的两个语句也将等效:

int i1 = del(1);

int i2 = del2(1);

              履行此代码后,i1 和 i2 的值都为 2。

1.7 dynamic 类型

dynamic 类型在 C# 中具有特殊含义。其用处在于允许进行动态绑定(在第 7.2.2 节中进行了详细介绍)。

dynamic 被视为与 object 相同,除以下这些方面:

  • 对 dynamic 类型的表达式进行的运算可以动态绑定(第 7.2.2 节)。
  • 类型推断(第 7.5.2 节)在 dynamic 和 object 都是候选项时,会优先斟酌前者。

由于此等效性,因此存在以下情况:

  • object 与 dynamic 之间,和对在将 dynamic 替换为 object 时相同的构造类型之间,存在隐式标识转换
  • 与 object 之间的隐式和显式转换也适用于 dynamic。
  • 在将 dynamic 替换为 object 时相同的方法签名视为是相同的签名

dynamic 类型在运行时与 object 没有区分。

dynamic 类型的表达式称为动态表达式 (dynamic expression)。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐