C# 2.0 Specification
来源:百度文库 编辑:神马文学网 时间:2024/04/29 18:48:39
C#
规范 2.0 版
2005 年 7 月
注意
© 2005 Microsoft Corporation.保留所有权利。
Microsoft、Windows、Visual Basic、Visual C# 和 Visual C++ 是 Microsoft Corporation 在美国和/或其他国家/地区的注册商标或商标。
本文提及的其他产品和公司名称可能是其各自所有者的商标。
目录
19. C# 2.0 简介......................................................................................................................................... 1
19.1 泛型................................................................................................................................................. 1
19.1.1 为什么要使用泛型?................................................................................................................ 1
19.1.2 创建和使用泛型........................................................................................................................ 2
19.1.3 泛型类型实例化........................................................................................................................ 3
19.1.4 约束........................................................................................................................................... 4
19.1.5 泛型方法................................................................................................................................... 5
19.2 匿名方法......................................................................................................................................... 6
19.2.1 方法组转换............................................................................................................................... 8
19.3 迭代器............................................................................................................................................. 8
19.4 分部类型........................................................................................................................................ 11
19.5 可空类型........................................................................................................................................ 12
20. 泛型................................................................................................................................................... 15
20.1 泛型类声明.................................................................................................................................... 15
20.1.1 类型形参................................................................................................................................. 15
20.1.2 实例类型................................................................................................................................. 16
20.1.3 基规范..................................................................................................................................... 17
20.1.4 泛型类的成员.......................................................................................................................... 17
20.1.5 泛型类中的静态字段............................................................................................................... 18
20.1.6 泛型类中的静态构造函数....................................................................................................... 18
20.1.7 访问受保护成员...................................................................................................................... 19
20.1.8 泛型类中的重载...................................................................................................................... 19
20.1.9 形参数组方法和类型形参....................................................................................................... 20
20.1.10 重写和泛型类........................................................................................................................ 20
20.1.11 泛型类中的运算符................................................................................................................. 21
20.1.12 泛型类中的嵌套类型............................................................................................................. 22
20.1.13 应用程序入口点.................................................................................................................... 23
20.2 泛型结构声明............................................................................................................................... 23
20.3 泛型接口声明............................................................................................................................... 23
20.3.1 所实现接口的唯一性............................................................................................................... 23
20.3.2 显式接口成员实现.................................................................................................................. 24
20.4 泛型委托声明............................................................................................................................... 25
20.5 构造类型....................................................................................................................................... 25
20.5.1 类型实参................................................................................................................................. 26
20.5.2 开放和封闭类型...................................................................................................................... 26
20.5.3 构造类型的基类和接口........................................................................................................... 27
20.5.4 构造类型的成员...................................................................................................................... 27
20.5.5 构造类型的可访问性............................................................................................................... 28
20.5.6 转换......................................................................................................................................... 28
20.5.7 using 别名指令......................................................................................................................... 29
20.5.8 属性......................................................................................................................................... 29
20.5.9 数组和泛型 IList 接口............................................................................................................. 29
20.6 泛型方法....................................................................................................................................... 30
20.6.1 泛型方法签名.......................................................................................................................... 31
20.6.2 虚泛型方法.............................................................................................................................. 31
20.6.3 调用泛型方法.......................................................................................................................... 33
20.6.4 类型实参推断.......................................................................................................................... 33
20.6.5 语法多义性.............................................................................................................................. 35
20.6.6 通过委托使用泛型方法........................................................................................................... 35
20.6.7 不能是泛型的成员.................................................................................................................. 36
20.7 约束............................................................................................................................................... 36
20.7.1 满足约束................................................................................................................................. 40
20.7.2 类型形参上的成员查找........................................................................................................... 41
20.7.3 类型形参和装箱...................................................................................................................... 41
20.7.4 涉及类型形参的转换............................................................................................................... 42
20.8 表达式和语句............................................................................................................................... 44
20.8.1 对象创建表达式...................................................................................................................... 44
20.8.2 typeof 运算符........................................................................................................................... 44
20.8.3 引用相等运算符...................................................................................................................... 45
20.8.4 is 运算符.................................................................................................................................. 46
20.8.5 as 运算符................................................................................................................................. 46
20.8.6 异常语句................................................................................................................................. 46
20.8.7 lock 语句.................................................................................................................................. 46
20.8.8 using 语句................................................................................................................................ 46
20.8.9 foreach 语句............................................................................................................................. 46
20.9 查找规则的修改........................................................................................................................... 47
20.9.1 命名空间和类型名称............................................................................................................... 47
20.9.2 成员查找................................................................................................................................. 49
20.9.3 适用函数成员.......................................................................................................................... 50
20.9.4 更好的函数成员...................................................................................................................... 50
20.9.5 简单名称................................................................................................................................. 51
20.9.6 成员访问................................................................................................................................. 52
20.9.7 方法调用................................................................................................................................. 54
20.10 右移语法变化............................................................................................................................. 55
21. 匿名方法......................................................................................................................................... 57
21.1 匿名方法表达式........................................................................................................................... 57
21.2 匿名方法签名............................................................................................................................... 57
21.3 匿名方法转换............................................................................................................................... 57
21.4 匿名方法块................................................................................................................................... 59
21.5 外层变量....................................................................................................................................... 59
21.5.1 捕获的外层变量...................................................................................................................... 59
21.5.2 局部变量实例化...................................................................................................................... 60
21.6 匿名方法计算............................................................................................................................... 62
21.7 委托实例相等性........................................................................................................................... 63
21.8 明确赋值....................................................................................................................................... 63
21.9 方法组转换................................................................................................................................... 64
21.10 委托创建表达式.......................................................................................................................... 65
21.11 实现示例..................................................................................................................................... 65
22. 迭代器............................................................................................................................................. 69
22.1 迭代器块....................................................................................................................................... 69
22.1.1 枚举器接口.............................................................................................................................. 69
22.1.2 可枚举接口.............................................................................................................................. 69
22.1.3 产生类型................................................................................................................................. 69
22.1.4 this 访问................................................................................................................................... 70
22.2 枚举器对象................................................................................................................................... 70
22.2.1 MoveNext 方法........................................................................................................................ 70
22.2.2 Current 属性............................................................................................................................. 71
22.2.3 Dispose 方法............................................................................................................................ 71
22.3 可枚举对象................................................................................................................................... 72
22.3.1 GetEnumerator 方法................................................................................................................. 72
22.4 yield 语句....................................................................................................................................... 72
22.4.1 明确赋值................................................................................................................................. 74
22.5 实现示例....................................................................................................................................... 74
23. 分部类型......................................................................................................................................... 81
23.1 分部声明....................................................................................................................................... 81
23.1.1 属性......................................................................................................................................... 81
23.1.2 修饰符..................................................................................................................................... 82
23.1.3 类型参数和约束...................................................................................................................... 82
23.1.4 基类......................................................................................................................................... 82
23.1.5 基接口..................................................................................................................................... 83
23.1.6 成员......................................................................................................................................... 83
23.2 名称绑定....................................................................................................................................... 84
24. 可空类型......................................................................................................................................... 85
24.1 可空类型....................................................................................................................................... 85
24.1.1 成员......................................................................................................................................... 85
24.1.2 默认值..................................................................................................................................... 86
24.1.3 值类型约束.............................................................................................................................. 86
24.2 转换.............................................................................................................................................. 86
24.2.1 null 文本转换........................................................................................................................... 86
24.2.2 可空转换................................................................................................................................. 86
24.2.3 装箱和取消装箱转换............................................................................................................... 87
24.2.4 允许的用户定义转换............................................................................................................... 87
24.2.5 用户定义转换的计算............................................................................................................... 88
24.2.6 提升的转换.............................................................................................................................. 88
24.2.7 用户定义的隐式转换............................................................................................................... 88
24.2.8 用户定义的显式转换............................................................................................................... 89
24.3 表达式.......................................................................................................................................... 90
24.3.1 提升运算符.............................................................................................................................. 90
24.3.2 允许的用户定义运算符........................................................................................................... 91
24.3.3 运算符重载解析...................................................................................................................... 91
24.3.4 相等操作符和空...................................................................................................................... 91
24.3.5 is 运算符.................................................................................................................................. 91
24.3.6 as 运算符................................................................................................................................. 92
24.3.7 复合赋值................................................................................................................................. 92
24.3.8 bool? 类型................................................................................................................................ 92
24.3.9 空合并运算符.......................................................................................................................... 93
25. 其他功能......................................................................................................................................... 95
25.1 属性访问器的可访问性................................................................................................................ 95
25.1.1 访问器声明.............................................................................................................................. 95
25.1.2 访问器的使用.......................................................................................................................... 96
25.1.3 重写和接口实现...................................................................................................................... 97
25.2 静态类.......................................................................................................................................... 97
25.2.1 静态类声明.............................................................................................................................. 97
25.2.2 引用静态类类型...................................................................................................................... 98
25.3 命名空间别名限定符.................................................................................................................... 98
25.3.1 限定的别名成员.................................................................................................................... 100
25.3.2 别名的唯一性........................................................................................................................ 101
25.4 Extern 别名................................................................................................................................... 102
25.4.1 Extern 别名指令..................................................................................................................... 103
25.5 Pragma 指令................................................................................................................................. 104
25.5.1 Pragma warning...................................................................................................................... 105
25.6 默认值表达式............................................................................................................................. 105
25.7 条件属性类................................................................................................................................. 106
25.8 固定大小缓冲区.......................................................................................................................... 107
25.8.1 固定大小缓冲区的声明......................................................................................................... 107
25.8.2 表达式中的固定大小缓冲区.................................................................................................. 108
25.8.3 Fixed 语句.............................................................................................................................. 109
25.8.4 明确赋值检查........................................................................................................................ 109
简介
C# 2.0 引入了几项语言扩展,其中包括泛型 (Generic)、匿名方法 (Anonymous Method)、迭代器 (Iterator)、分部类型 (Partial Type) 和可空类型 (Nullable Type)。
· 泛型可以让类、结构、接口、委托和方法按它们存储和操作的数据的类型进行参数化。泛型很有用,因为它们能提供更强的编译时类型检查,减少数据类型之间的显式转换,以及装箱操作和运行时的类型检查。
· 在需要委托值的地方,匿名方法允许以“内联”方式编写代码块。匿名方法类似于 Lisp 编程语言中的 lambda 函数。C# 2.0 支持创建“closure”,其中的匿名方法可以访问外层局部变量和参数。
· 迭代器是执行递增计算并产生一系列值的方法。迭代器使类型可以简便地指定foreach 语句循环访问其元素的方式。
· 分部类型允许将类、结构和接口划分为多个部分,存储在不同的源文件中,以便于开发和维护。此外,分部类型允许将计算机生成的类型部分和用户编写的类型部分互相分开,以便更容易地扩充工具生成的代码。
· 可空类型表示可能未知的值。可空类型支持其基础类型的所有值以及一个附加的空状态。任何值类型均可作为可空类型的基础类型。可空类型支持与其基础类型相同的转换和运算符,另外还提供类似于 SQL 的空值传播。
本章将简要介绍这些新功能。之后,接下来的 5 章详细介绍这些功能的完整技术规范。最后一章介绍 C# 2.0 中引入的一些次要的扩展。
C# 2.0 中的语言扩展在设计上充分考虑并确保与现有代码的最大兼容性。例如,尽管 C# 2.0 在某些上下文中为单词 where、yield 和 partial 提供了特殊含义,但是这些单词仍可用作标识符。实际上,C# 2.0 没有添加新的关键字,因为这样的关键字可能与现有代码中的标识符冲突。
有关 C# 语言的最新信息以及如何为本文档提供反馈意见的说明,请访问“C# 语言主页”(http://msdn.microsoft.com/vcsharp/language)。
泛型可以让类、结构、接口、委托和方法按它们存储和操作的数据的类型进行参数化。使用过 Eiffel 或 Ada 泛型的用户或 C++ 模板的用户很快就能熟悉 C# 泛型,而且这些用户会发现 C# 泛型较之过去这些语言更加简便易用。
如果没有泛型,通用数据结构可通过使用类型 object 实现任何数据类型的存储。例如,下面是一个简单的 Stack 类,它将数据存储在一个 object 数组中,它的两个方法 Push 和 Pop 分别使用 object接受和返回数据:
public class Stack
{
object[] items;
int count;
public void Push(object item) {...}
public object Pop() {...}
}
虽然使用类型 object 使得 Stack 类非常灵活,但这种方式仍存在某些缺陷。例如,我们可以将任何类型的值(例如一个 Customer 实例)推入堆栈。但是,当从堆栈中检索某个值时,必须将 Pop 方法的结果显式强制转换回相应的类型,这样的代码编写起来颇为繁琐,而且在运行时执行的类型检查会造成额外的开销从而影响性能:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
再比如,当我们将一个值类型(例如 int)的值传递给 Push 方法时,则该值将自动被装箱。当以后检索该 int 时,必须使用显式类型强制转换将其取消装箱:
Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();
这样的装箱和取消装箱操作由于涉及动态内存分配和运行时类型检查而额外增加了性能开销。
上述 Stack 类还有一个潜在的问题就是我们无法对放到堆栈上的数据的种类施加限制。实际上,可能会发生这种情况:将一个 Customer 实例推入堆栈,而在检索到该实例之后却意外地将它强制转换为错误的类型:
Stack stack = new Stack();
stack.Push(new Customer());
string s = (string)stack.Pop();
虽然上面的代码错误使用了 Stack 类,但是从技术角度讲该代码可以视作是正确的,编译器不会报告编译时错误。这个问题在该代码被执行之前不会暴露出来,但在执行该代码时会引发 InvalidCastException。
显然,如果能够指定元素类型,Stack 类将能够从中受益。有了泛型,我们便可以做到这一点。
泛型提供了一种新的创建类型的机制,使用泛型创建的类型将带有类型形参 (type parameter)。下面的示例声明一个带有类型形参 T 的泛型 Stack 类。类型形参在 < 和 > 分隔符中指定并放置在类名后。Stack 的实例的类型由创建时所指定的类型确定,该实例将存储该类型的数据而不进行数据类型转换。这有别于同 object 之间的相互转换。类型形参 T 只起占位符的作用,直到在使用时为其指定了实际类型。注意,这里的 T 用作内部项数组的元素类型、传递给 Push 方法的参数类型和 Pop 方法的返回类型:
public class Stack
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}
在使用泛型类 Stack 时,将指定用于替换 T 的实际类型。在下面的示例中,指定了 int 作为 T 的类型实参 (type argument):
Stack stack = new Stack();
stack.Push(3);
int x = stack.Pop();
Stack 类型称为构造类型 (constructed type)。在 Stack 类型中,出现的每个 T 都被替换为类型实参 int。在创建 Stack 的实例后,items 数组的本机存储是 int[] 而不是 object[]。无疑,这比非泛型的 Stack提供了更高的存储效率。同样,Stack 的 Push 和 Pop 方法所操作的也是 int 类型的值。如果将其他类型的值推入堆栈则产生编译时错误。而且在检索值时也不再需要将它们显式强制转换为原始类型。
泛型提供了强类型机制,这意味着如果将一个 int 值推入 Customer 对象的堆栈将导致错误。正如 Stack 仅限于操作 int 值一样,Stack 仅限于操作 Customer 对象,编译器将对下面示例中的最后两行报告错误:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3); // Type mismatch error
int x = stack.Pop(); // Type mismatch error
泛型类型声明可以含有任意数目的类型形参。上面的 Stack 示例只有一个类型形参,而一个泛型 Dictionary 类可能具有两个类型形参,一个用于键的类型,一个用于值的类型:
public class Dictionary
{
public void Add(K key, V value) {...}
public V this[K key] {...}
}
在使用上述 Dictionary 时,必须提供两个类型实参:
Dictionary dict = new Dictionary();
dict.Add("Peter", new Customer());
Customer c = dict["Peter"];
与非泛型类型类似,泛型类型的编译表示形式也是中间语言 (IL) 指令和元数据。当然,泛型类型的表示形式还要对类型形参的存在和使用进行编码。
在应用程序第一次创建构造泛型类型(例如 Stack)的实例时,.NET 公共语言运行库的实时 (JIT) 编译器将泛型 IL 和元数据转换为本机代码,并在该过程中将类型形参替换为实际类型。然后,对该构造泛型类型的后续引用将使用相同的本机代码。从泛型类型创建特定构造类型的过程称为泛型类型实例化 (generic type instantiation)。
对于值类型,.NET 公共语言运行库为每次泛型类型实例化单独创建专用的本机代码副本。而对于所有的引用类型,则共享该本机代码的单个副本(因为在本机代码级别,引用不过是具有相同表示形式的
指针)。
通常,泛型类的作用并不仅仅是根据类型形参存储数据。泛型类常常需要调用对象上的方法,对象的类型由类型形参给出。例如,Dictionary 类中的 Add 方法可能需要使用 CompareTo 方法对键进行比较:
public class Dictionary
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...} // Error, no CompareTo method
...
}
}
由于为 K 指定的类型实参可以是任何类型,对于 key 参数,能够假设存在的成员仅限于类型 object 声明的成员,例如 Equals、GetHashCode 和 ToString;因此上面的示例将发生编译时错误。当然,我们可以将 key 参数强制转换为含有 CompareTo 方法的类型。例如,可以将 key 参数强制转换为 IComparable:
public class Dictionary
{
public void Add(K key, V value)
{
...
if (((IComparable)key).CompareTo(x) < 0) {...}
...
}
}
虽然这种解决方案可行,但它需要在运行时动态检查类型,因此会增加开销。另外它还将错误报告推迟到运行时,当键未实现 IComparable 时引发 InvalidCastException。
为了提供更强的编译时类型检查并减少类型强制转换,C# 允许为每个类型形参提供一个可选的约束 (constraint) 列表。类型形参约束指定了一个要求,类型必须满足该要求才能用作该类型形参的实参。约束使用单词 where 进行声明,后跟一个类型形参和一个冒号,再跟着一个逗号分隔的列表,列表项可以是类类型、接口类型甚或类型形参(还可以是特殊引用类型、值类型和构造函数约束)。
为了让 Dictionary 类能够确保键总是实现 IComparable,可在类声明中为类型形参 K 指定一个约束,如下所示:
public class Dictionary where K: IComparable
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
有了这个声明后,编译器将确保为 K 提供的任何类型实参均为一个实现了 IComparable 的类型。不仅如此,此约束声明还避免了在调用 CompareTo 方法之前显式将键参数强制转换为 IComparable;满足此类型形参约束的类型的所有成员都可作为该类型形参类型的值直接使用。
对于一个给定的类型形参,作为约束的接口和类型形参的数目不受限制,但只能有一个类。每个受约束的类型形参具有单独的 where 子句。在下面的示例中,类型形参 K 具有两个接口约束,而类型形参 E 具有一个类类型约束和一个构造函数约束:
public class EntityTable
where K: IComparable, IPersistable
where E: Entity, new()
{
public void Add(K key, E entity)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
在上面示例中,构造函数约束 new() 确保用作 E 的类型实参的类型具有无参数的公共构造函数,这样,泛型类便可以使用 new E() 创建该类型的实例。
需指出的是,类型形参约束应该谨慎使用。虽然它们提供更强的编译时类型检查,并在某些情况下改进了性能,但是它们也使泛型类型的使用受到限制。例如,泛型类 List 可能约束 T 必须实现 IComparable,以便 List 的 Sort 方法能够对项进行比较。但是,即使在某些情形下 Sort 方法根本没有被调用,也会由于上述约束的存在而导致未实现 IComparable 的类型无法使用 List。
在有些情况下,并不是整个类都需要某个类型形参,而是仅在某个特定方法中需要。通常,在创建采用泛型类型作为形参的方法时会遇到这种情况。例如,在使用上述 Stack 类时,常见的使用模式可能是使用一行代码将多个值推入堆栈,我们可以很方便地编写一个方法在单个调用中完成此任务。对于特定的构造类型,例如 Stack,该方法如下所示:
void PushMultiple(Stack stack, params int[] values) {
foreach (int value in values) stack.Push(value);
}
然后,可以使用此方法将多个 int 值推入 Stack:
Stack stack = new Stack();
PushMultiple(stack, 1, 2, 3, 4);
但是,上面的方法仅适用于特定的构造类型 Stack。为了将其用于任何 Stack,必须将该方法编写为一个泛型方法 (generic method)。泛型方法在方法名后面的 < 和 > 分隔符中指定了一个或多个类型形参。这些类型形参可以在形参列表、返回类型和方法体内使用。泛型 PushMultiple 方法如下
所示:
void PushMultiple(Stack stack, params T[] values) {
foreach (T value in values) stack.Push(value);
}
使用此泛型方法,可以将多个项推入任何 Stack。在调用泛型方法时,类型实参在方法调用中的尖括号中给出。例如:
Stack stack = new Stack();
PushMultiple(stack, 1, 2, 3, 4);
此泛型 PushMultiple 方法较之前一个版本更具可重用性,因为它适用于任何 Stack。但由于必须以类型实参的方式为该方法提供所需的 T,使其调用起来似乎没有前一版本方便。但是在许多情况下,编译器能够使用称为“类型推断”(type inferencing) 的过程,从传递给方法的其他实参推断出正确的类型实参。在上面的示例中,由于第一个标准实参是 Stack 类型,后续的几个实参为 int 类型,编译器能够推断出类型形参一定是 int。这样,可以在不指定类型形参的情况下调用泛型 PushMultiple 方法:
Stack stack = new Stack();
PushMultiple(stack, 1, 2, 3, 4);
事件处理程序和其他回调方法通常仅通过委托机制进行调用,而从不直接进行调用。所以,我们目前还是只能将事件处理程序代码及回调代码置于特定的方法中,并显式为方法创建委托。而匿名方法 (anonymous method) 则不同,它允许与委托关联的代码以“内联”方式写入使用委托的位置,从而方便地将代码直接“绑定”到委托实例。除了这种便利之外,匿名方法还能够对包含它的函数成员的局部状态进行共享访问。而要使用具名方法实现同样的状态共享,需要将局部变量“提升”到手动编写的辅助类实例的字段中。
下面的示例显示一个包含列表框、文本框和按钮的简单输入窗体。单击该按钮时,一个包含文本框中所示文本的项将被添加到列表框中。
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender, EventArgs e) {
listBox.Items.Add(textBox.Text);
}
}
虽然在响应该按钮的 Click 事件时只执行了一条语句,但是必须将该语句提取到一个具有完整参数列表的单独方法中,并且必须手动创建一个引用该方法的 EventHandler 委托。使用匿名方法,该事件处理代码明显变得更简洁了:
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += delegate {
listBox.Items.Add(textBox.Text);
};
}
}
匿名方法由关键字 delegate、可选的参数列表和包含在 { 和 } 分隔符中的语句列表组成。上述示例中的匿名方法没有使用委托提供的参数,因此可以省略参数列表。若要获得对参数的访问,该匿名方法可以包括参数列表:
addButton.Click += delegate(object sender, EventArgs e) {
MessageBox.Show(((Button)sender).Text);
};
在上面的示例中,发生了从该匿名方法到 EventHandler 委托类型(Click 事件的类型)的隐式转换。能够进行该隐式转换是因为委托类型的参数列表和返回类型与匿名方法兼容。具体的兼容规则如下:
· 如果下列条件之一成立,则委托的参数列表与匿名方法兼容:
o 匿名方法没有参数列表并且委托没有 out 参数。
o 匿名方法包含的参数列表与委托的参数列表在数目、类型和修饰符方面都精确匹配。
· 如果下列条件之一成立,则委托的返回类型与匿名方法兼容:
o 委托的返回类型为 void,并且匿名方法没有 return 语句或只有无表达式的 return 语句。
o 委托的返回类型不为 void,并且与匿名方法中的所有 return 语句关联的表达式都可隐式转换为委托的返回类型。
委托的参数列表和返回类型都必须与匿名方法兼容才能进行从匿名方法到委托类型的隐式转换。
下面的示例使用匿名方法编写“内联”函数。匿名方法作为 Function 委托类型的参数来传递。
using System;
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
static double[] MultiplyAllBy(double[] a, double factor) {
return Apply(a, delegate(double x) { return x * factor; });
}
static void Main() {
double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, delegate(double x) { return x * x; });
double[] doubles = MultiplyAllBy(a, 2.0);
}
}
Apply 方法将给定的 Function 应用于 double[] 的各元素,并返回含有结果的 double[]。在 Main 方法中,传递给 Apply 的第二个参数是与 Function 委托类型兼容的匿名方法。此匿名方法完成一个简单的操作,即返回其实参的平方,因此该 Apply 调用的结果是包含 a 中各个值的平方的 double[]。
MultiplyAllBy 方法返回一个 double[],其中的值分别为实参数组 a 中的每个值乘以给定的 factor 后所得的结果。为了获得所需的结果,MultiplyAllBy 调用了 Apply 方法,并传递一个用来将实参 x 乘以 factor 的匿名方法。
其作用域包含某匿名方法的局部变量和参数称为该匿名方法的外层变量 (outer variable)。在 MultiplyAllBy 方法中,a 和 factor 是传递给 Apply 的匿名方法的外层变量,并且由于该匿名方法引用 factor,我们称 factor 被该匿名方法捕获 (captured)。通常,局部变量的生存期仅限于该变量所关联的代码块或语句的执行期间。但是,被捕获的外层变量的生存期将至少延长至引用匿名方法的委托可以被垃圾回收为止。
正如前一节所述,匿名方法可隐式转换为与之兼容的委托类型。C# 2.0 允许对方法组进行这种相同类型的转换,这样,几乎在所有情况下都可以省略显式的委托实例化。例如,语句
addButton.Click += new EventHandler(AddClick);
Apply(a, new Function(Math.Sin));
可改写为
addButton.Click += AddClick;
Apply(a, Math.Sin);
当使用较短的形式时,编译器自动推断要实例化的委托类型,而效果与使用较长的形式是一样的。
C# foreach 语句用于循环访问可枚举 (enumerable) 集合的元素。为了成为可枚举的类型,集合必须具有返回枚举器 (enumerator) 的无参数的 GetEnumerator 方法。一般而言,枚举器不易实现,但是通过使用迭代器可以显著简化该任务。
迭代器 (iterator) 是一个产生 (yield) 有序值序列的语句块。迭代器与普通语句块的区别在于迭代器存在一个或多个 yield 语句:
· yield return 语句产生迭代的下一个值。
· yield break 语句指示迭代完成。
只要函数成员的返回类型是枚举器接口 (enumerator interface) 之一或可枚举接口 (enumerable interface) 之一,迭代器就可用作该函数成员的函数体:
· 枚举器接口为 System.Collections.IEnumerator 和从 System.Collections.Generic.IEnumerator 构造的类型。
· 可枚举接口为 System.Collections.IEnumerable 和从 System.Collections.Generic.IEnumerable 构造的类型。
迭代器并非一种成员,而是一种实现函数成员的手段,了解这一点很重要。通过迭代器实现的成员能够被其他可能使用也可能不使用迭代器实现的成员重写或重载。
下面的 Stack 类使用一个迭代器实现其 GetEnumerator 方法。该迭代器以自顶向下的顺序枚举堆栈的元素。
using System.Collections.Generic;
public class Stack: IEnumerable
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator GetEnumerator() {
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
}
GetEnumerator 方法的存在使得 Stack 成为可枚举类型,这样,我们就可以在 foreach 语句中使用 Stack 的实例。下面的示例将值 0 至 9 推入一个整数堆栈,然后使用 foreach 循环按自顶向下的顺序显示这些值。
using System;
class Test
{
static void Main() {
Stack stack = new Stack();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack) Console.Write("{0} ", i);
Console.WriteLine();
}
}
该示例的输出为:
9 8 7 6 5 4 3 2 1 0
foreach 语句隐式调用集合的无参数 GetEnumerator 方法获得枚举器。一个集合只能定义一个这样的无参数 GetEnumerator 方法,但是可以有多种枚举方式以及多种通过参数控制枚举的方式。在这种情况下,集合可以使用迭代器实现多个返回可枚举接口类型之一的属性或方法。例如,Stack 可引入 IEnumerable 类型的两个新属性 TopToBottom 和 BottomToTop:
using System.Collections.Generic;
public class Stack: IEnumerable
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator GetEnumerator() {
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
public IEnumerable TopToBottom {
get {
return this;
}
}
public IEnumerable BottomToTop {
get {
for (int i = 0; i < count; i++) {
yield return items[i];
}
}
}
}
TopToBottom 属性的 get 访问器直接返回 this,因为该堆栈本身是可枚举的。BottomToTop 属性返回一个使用 C# 迭代器实现的可枚举接口类型。下面的示例显示如何使用这两个属性以任一顺序枚举堆栈元素:
using System;
class Test
{
static void Main() {
Stack stack = new Stack();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack.TopToBottom) Console.Write("{0} ", i);
Console.WriteLine();
foreach (int i in stack.BottomToTop) Console.Write("{0} ", i);
Console.WriteLine();
}
}
当然,这些属性也可以在 foreach 语句之外使用。下面的示例将调用这些属性的结果传递给一个单独的 Print 方法。该示例还演示了一个用作 FromToBy 方法(该方法带有参数)的方法体的迭代器:
using System;
using System.Collections.Generic;
class Test
{
static void Print(IEnumerable collection) {
foreach (int i in collection) Console.Write("{0} ", i);
Console.WriteLine();
}
static IEnumerable FromToBy(int from, int to, int by) {
for (int i = from; i <= to; i += by) {
yield return i;
}
}
static void Main() {
Stack stack = new Stack();
for (int i = 0; i < 10; i++) stack.Push(i);
Print(stack.TopToBottom);
Print(stack.BottomToTop);
Print(FromToBy(10, 20, 2));
}
}
该示例的输出为:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
10 12 14 16 18 20
,即一个不带参数并返回一个枚举器接口类型的 GetEnumerator 方法。可枚举接口起到“枚举器工厂”的作用。如果正确实现了可枚举接口,每次调用它们的 GetEnumerator 方法时,都会生成一个独立的枚举器。假设在两次调用 GetEnumerator 期间可枚举接口类型的内部状态没有改变,则所返回的两个枚举器应以相同的顺序产生相同的枚举值集合。即使枚举器的生存期重叠,这个结论也应该有效,如下面的代码示例所示:
using System;
using System.Collections.Generic;
class Test
{
static IEnumerable FromTo(int from, int to) {
while (from <= to) yield return from++;
}
static void Main() {
IEnumerable e = FromTo(1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}
上面的代码可输出整数 1 至 10 的简单乘法表。注意,FromTo 方法仅被调用一次来生成可枚举的 e。但是,e.GetEnumerator() 被调用多次(由 foreach 语句调用)以生成多个等效的枚举器。这些枚举器全都封装了在 FromTo 的声明中指定的迭代器代码。注意该迭代器代码将修改 from 参数。然而,这些枚举器独立工作,因为每个枚举器都会获得它自己的 from 和 to 参数的副本。枚举器之间的过渡状态的共享是若干常见细微缺陷之一,在实现可枚举接口和枚举器时应该避免。C# 迭代器旨在帮助避免这些问题,并以简单、直观的方法实现可靠的可枚举接口和枚举器。
虽然在单个文件中维护某个类型的所有源代码是个很好的编程习惯,但是有时一个类型会变得非常大,在这种情况下,这种做法反而成为了一种不切实际的限制。此外,程序员经常使用源代码生成器产生应用程序的初始结构,然后修改结果代码。遗憾的是,当将来某个时候再次发出源代码时,现有修改将被改写。
分部类型 (Partial type) 允许将类、结构和接口划分为多个部分,存储在不同的源文件中,以便于开发和维护。此外,分部类型允许将计算机生成的类型部分和用户编写的类型部分互相分开,以便更容易地扩充工具生成的代码。
当通过多个部分来定义类型时,将使用新增的类型修饰符 partial。下面是一个分为两部分来实现的分部类示例。这里,第一部分是通过数据库映射工具由计算机生成的,而第二部分是手动编写的,所以,可将这两个部分分别存储在不同的源文件中:
public partial class Customer
{
private int id;
private string name;
private string address;
private List orders;
public Customer() {
...
}
}
public partial class Customer
{
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
当将上述两个部分一起编译时,结果代码与将该类编写为单个单元时相同:
public class Customer
{
private int id;
private string name;
private string address;
private List orders;
public Customer() {
...
}
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
分部类型的所有部分必须一起编译,以使这些部分可在编译时被合并。特别指出的是,分部类型不允许对已经编译的类型进行扩展。
在与数据库交互时,支持所有类型(包括值类型)的可空值是很重要的,而一直以来通用编程语言很少或没有提供这方面的支持。在没有直接语言支持的情况下,虽然也存在许多用于处理空值和值类型的方法,但都存在缺点。例如,一种方法是使用“特殊”值(例如将 −1 用于整数)指示空值,但是这种方法只有在能够确定未使用的值的情况下有效。另一种方法是在单独的字段或变量中维护布尔空值指示符,但是这种方法不是很适合参数和返回值。第三种方法是使用一组用户定义的可空类型,但是这仅适用于属于闭集的类型集合。C# 的可空类型 (nullable type) 通过为所有值类型的可空形式提供完整和集成的支持,解决了这个由来已久的问题。
可空类型是使用 类型修饰符来构造的。例如,int? 是预定义类型 int 的可空形式。可空类型的基础类型必须是非可空的值类型。
可空类型是一个组合了基础类型的值和布尔空值指示符的结构。可空类型的实例具有两个公共只读属性:bool 类型的 HasValue 和可空类型的基础类型的 Value。HasValue 对所有非空实例都为 true,对空实例为 false。当 HasValue 为 true 时,Value 属性返回所包含的值。当 HasValue 为 false 时,尝试访问 Value 属性将引发异常。
可从任何非可空值类型隐式转换到该类型的可空形式。此外,还可从 null 文本隐式转换到任何可空类型。在下面的示例中
int? x = 123;
int? y = null;
if (x.HasValue) Console.WriteLine(x.Value);
if (y.HasValue) Console.WriteLine(y.Value);
int 值 123 和 null 文本被隐式转换为可空类型 int?。该示例针对 x 输出 123,但是第二个 Console.WriteLine 未执行,因为 y.HasValue 为 false。
可空转换 (Nullable conversion) 和提升转换 (lifted conversion) 允许对非可空值类型进行操作的预定义转换和用户定义转换也能够用于这些类型的可空形式。同样,提升运算符 (lifted operator) 允许用于非可空值类型的预定义运算符和用户定义运算符也能够用于这些类型的可空形式。
对于从非可空值类型 S 到非可空值类型 T 的每个预定义转换,将自动存在从 S? 到 T? 的预定义可空转换。这种可空转换是基础转换的一种空传播 (null propagating) 形式:它将空的源值直接转换为空的目标值,但是对其他值则执行基础非空转换。此外还进一步提供了从 S 到 T? 和从 S? 到 T 的可空转换,后一种转换作为在源值为空时将引发异常的显式转换。
下面是一些可空转换示例。
int i = 123;
int? x = i; // int --> int?
double? y = x; // int? --> double?
int? z = (int?)y; // double? --> int?
int j = (int)z; // int? --> int
当源和目标类型均为非可空值类型时,用户定义的转换运算符将具有提升形式。源和目标类型将添加一个 修饰符以创建提升形式。与预定义的可空转换类似,提升转换运算符也可传播空值。
当操作数类型和结果类型全都为非可空值类型时,非比较运算符具有提升形式。对于非比较运算符,每个操作数类型和结果类型将添加一个 修饰符以创建提升形式。例如,接受两个 int 操作数并返回一个 int 的预定义 + 运算符的提升形式是接受两个 int? 操作数并返回一个 int? 的运算符。与提升转换类似,提升非比较运算符也进行空传播:如果提升运算符的任一操作数为空,则结果为空值。
下面的示例使用 + 提升运算符将两个 int? 值相加:
int? x = GetNullableInt();
int? y = GetNullableInt();
int? z = x + y;
对 z 的赋值实际上等价于:
int? z = x.HasValue && y.HasValue ? x.Value + y.Value : (int?)null;
由于存在从非可空值类型到其可空形式的隐式转换,当只有一个操作数是可空类型时,提升运算符同样适用。下面的示例使用与上述示例相同的提升后的 + 运算符:
int? x = GetNullableInt();
int? y = x + 1;
如果 x 为空,则 y 被赋值为空。否则,y 被赋以 x 加一的值。
C# 的可空转换、提升转换和提升非比较运算符的空传播语义非常类似于 SQL 中的对应转换和运算符。但是,C# 的提升比较运算符产生标准的布尔结果而不是引入 SQL 的三值布尔逻辑。
当操作数类型均为非可空值类型并且结果类型为 bool 时,比较运算符(==、!=、<、>、<=、>=)具有提升形式。比较运算符的提升形式是通过向每个操作数类型添加一个 修饰符(但是不添加到结果类型)构成的。== 和 != 运算符的提升形式将两个空值视为相等,并且空值不等于非空值。如果一个为空或者两个操作数都为空,<、>、<= 和 >= 运算符的提升形式返回 false。
当 == 或 != 运算符的操作数之一为 null 文本时,另一个操作数可以是任何可空类型,而不管基础值类型是否实际声明了该运算符。在没有为运算符 == 或 != 提供实现的情况下,操作数的 HasValue 属性的检查将被替换。此规则的效果在于,类似如下的语句
if (x == null) Console.WriteLine("x is null");
if (x != null) Console.WriteLine("x is non-null");
对于任何可空类型或引用类型的 x 都是允许的,从而为可为空值的所有类型执行空值检查提供一种公共的方法。
另外还提供了空合并运算符 (null coalescing operator) 。如果 a 为非空,则 a b 的结果为 a;否则结果为 b。从效果看,b 提供了要在 a 为空时使用的值。
当 a 为可空类型而 b 为非可空类型时,只要操作数类型之间存在适当的隐式转换,a b 将返回非可空值。在下面的示例中
int? x = GetNullableInt();
int? y = GetNullableInt();
int? z = x ?? y;
int i = z ?? -1;
x y 的类型为 int?,但是 z -1 的类型为 int。后一种运算特别方便,因为它从类型移除 ,同时还提供要在为空值时使用的默认值。
空合并运算符也适用于引用类型。下面的示例
string s = GetStringValue();
Console.WriteLine(s ?? "Unspecified");
输出 s 的值,或在 s 为空值时输出 Unspecified。
泛型类声明是一种类的声明,它需要提供类型实参才能构成实际类型。
类声明可以有选择地定义类型形参:
class-declaration:
attributesopt class-modifiersopt class identifier type-parameter-listopt class-baseopt
type-parameter-constraints-clausesopt class-body ;opt
只有提供了一个 type-parameter-list,才可以为这个类声明提供 type-parameter-constraints-clauses(第 20.7 节)。
提供了 type-parameter-list 的类声明是一个泛型类声明。此外,任何嵌套在泛型类声明或泛型结构声明中的类本身就是一个泛型类声明,因为必须为包含类型提供类型形参才能创建构造类型。
除了明确指出的地方外,泛型类声明与非泛型类声明遵循相同的规则。泛型类声明可嵌套在非泛型类声明中。
使用构造类型 (constructed type)(第 20.4 节)引用泛型类。给定下面的泛型类声明
class List {}
List、List 和 List
规范 2.0 版
2005 年 7 月
注意
© 2005 Microsoft Corporation.保留所有权利。
Microsoft、Windows、Visual Basic、Visual C# 和 Visual C++ 是 Microsoft Corporation 在美国和/或其他国家/地区的注册商标或商标。
本文提及的其他产品和公司名称可能是其各自所有者的商标。
目录
19. C# 2.0 简介......................................................................................................................................... 1
19.1 泛型................................................................................................................................................. 1
19.1.1 为什么要使用泛型?................................................................................................................ 1
19.1.2 创建和使用泛型........................................................................................................................ 2
19.1.3 泛型类型实例化........................................................................................................................ 3
19.1.4 约束........................................................................................................................................... 4
19.1.5 泛型方法................................................................................................................................... 5
19.2 匿名方法......................................................................................................................................... 6
19.2.1 方法组转换............................................................................................................................... 8
19.3 迭代器............................................................................................................................................. 8
19.4 分部类型........................................................................................................................................ 11
19.5 可空类型........................................................................................................................................ 12
20. 泛型................................................................................................................................................... 15
20.1 泛型类声明.................................................................................................................................... 15
20.1.1 类型形参................................................................................................................................. 15
20.1.2 实例类型................................................................................................................................. 16
20.1.3 基规范..................................................................................................................................... 17
20.1.4 泛型类的成员.......................................................................................................................... 17
20.1.5 泛型类中的静态字段............................................................................................................... 18
20.1.6 泛型类中的静态构造函数....................................................................................................... 18
20.1.7 访问受保护成员...................................................................................................................... 19
20.1.8 泛型类中的重载...................................................................................................................... 19
20.1.9 形参数组方法和类型形参....................................................................................................... 20
20.1.10 重写和泛型类........................................................................................................................ 20
20.1.11 泛型类中的运算符................................................................................................................. 21
20.1.12 泛型类中的嵌套类型............................................................................................................. 22
20.1.13 应用程序入口点.................................................................................................................... 23
20.2 泛型结构声明............................................................................................................................... 23
20.3 泛型接口声明............................................................................................................................... 23
20.3.1 所实现接口的唯一性............................................................................................................... 23
20.3.2 显式接口成员实现.................................................................................................................. 24
20.4 泛型委托声明............................................................................................................................... 25
20.5 构造类型....................................................................................................................................... 25
20.5.1 类型实参................................................................................................................................. 26
20.5.2 开放和封闭类型...................................................................................................................... 26
20.5.3 构造类型的基类和接口........................................................................................................... 27
20.5.4 构造类型的成员...................................................................................................................... 27
20.5.5 构造类型的可访问性............................................................................................................... 28
20.5.6 转换......................................................................................................................................... 28
20.5.7 using 别名指令......................................................................................................................... 29
20.5.8 属性......................................................................................................................................... 29
20.5.9 数组和泛型 IList 接口............................................................................................................. 29
20.6 泛型方法....................................................................................................................................... 30
20.6.1 泛型方法签名.......................................................................................................................... 31
20.6.2 虚泛型方法.............................................................................................................................. 31
20.6.3 调用泛型方法.......................................................................................................................... 33
20.6.4 类型实参推断.......................................................................................................................... 33
20.6.5 语法多义性.............................................................................................................................. 35
20.6.6 通过委托使用泛型方法........................................................................................................... 35
20.6.7 不能是泛型的成员.................................................................................................................. 36
20.7 约束............................................................................................................................................... 36
20.7.1 满足约束................................................................................................................................. 40
20.7.2 类型形参上的成员查找........................................................................................................... 41
20.7.3 类型形参和装箱...................................................................................................................... 41
20.7.4 涉及类型形参的转换............................................................................................................... 42
20.8 表达式和语句............................................................................................................................... 44
20.8.1 对象创建表达式...................................................................................................................... 44
20.8.2 typeof 运算符........................................................................................................................... 44
20.8.3 引用相等运算符...................................................................................................................... 45
20.8.4 is 运算符.................................................................................................................................. 46
20.8.5 as 运算符................................................................................................................................. 46
20.8.6 异常语句................................................................................................................................. 46
20.8.7 lock 语句.................................................................................................................................. 46
20.8.8 using 语句................................................................................................................................ 46
20.8.9 foreach 语句............................................................................................................................. 46
20.9 查找规则的修改........................................................................................................................... 47
20.9.1 命名空间和类型名称............................................................................................................... 47
20.9.2 成员查找................................................................................................................................. 49
20.9.3 适用函数成员.......................................................................................................................... 50
20.9.4 更好的函数成员...................................................................................................................... 50
20.9.5 简单名称................................................................................................................................. 51
20.9.6 成员访问................................................................................................................................. 52
20.9.7 方法调用................................................................................................................................. 54
20.10 右移语法变化............................................................................................................................. 55
21. 匿名方法......................................................................................................................................... 57
21.1 匿名方法表达式........................................................................................................................... 57
21.2 匿名方法签名............................................................................................................................... 57
21.3 匿名方法转换............................................................................................................................... 57
21.4 匿名方法块................................................................................................................................... 59
21.5 外层变量....................................................................................................................................... 59
21.5.1 捕获的外层变量...................................................................................................................... 59
21.5.2 局部变量实例化...................................................................................................................... 60
21.6 匿名方法计算............................................................................................................................... 62
21.7 委托实例相等性........................................................................................................................... 63
21.8 明确赋值....................................................................................................................................... 63
21.9 方法组转换................................................................................................................................... 64
21.10 委托创建表达式.......................................................................................................................... 65
21.11 实现示例..................................................................................................................................... 65
22. 迭代器............................................................................................................................................. 69
22.1 迭代器块....................................................................................................................................... 69
22.1.1 枚举器接口.............................................................................................................................. 69
22.1.2 可枚举接口.............................................................................................................................. 69
22.1.3 产生类型................................................................................................................................. 69
22.1.4 this 访问................................................................................................................................... 70
22.2 枚举器对象................................................................................................................................... 70
22.2.1 MoveNext 方法........................................................................................................................ 70
22.2.2 Current 属性............................................................................................................................. 71
22.2.3 Dispose 方法............................................................................................................................ 71
22.3 可枚举对象................................................................................................................................... 72
22.3.1 GetEnumerator 方法................................................................................................................. 72
22.4 yield 语句....................................................................................................................................... 72
22.4.1 明确赋值................................................................................................................................. 74
22.5 实现示例....................................................................................................................................... 74
23. 分部类型......................................................................................................................................... 81
23.1 分部声明....................................................................................................................................... 81
23.1.1 属性......................................................................................................................................... 81
23.1.2 修饰符..................................................................................................................................... 82
23.1.3 类型参数和约束...................................................................................................................... 82
23.1.4 基类......................................................................................................................................... 82
23.1.5 基接口..................................................................................................................................... 83
23.1.6 成员......................................................................................................................................... 83
23.2 名称绑定....................................................................................................................................... 84
24. 可空类型......................................................................................................................................... 85
24.1 可空类型....................................................................................................................................... 85
24.1.1 成员......................................................................................................................................... 85
24.1.2 默认值..................................................................................................................................... 86
24.1.3 值类型约束.............................................................................................................................. 86
24.2 转换.............................................................................................................................................. 86
24.2.1 null 文本转换........................................................................................................................... 86
24.2.2 可空转换................................................................................................................................. 86
24.2.3 装箱和取消装箱转换............................................................................................................... 87
24.2.4 允许的用户定义转换............................................................................................................... 87
24.2.5 用户定义转换的计算............................................................................................................... 88
24.2.6 提升的转换.............................................................................................................................. 88
24.2.7 用户定义的隐式转换............................................................................................................... 88
24.2.8 用户定义的显式转换............................................................................................................... 89
24.3 表达式.......................................................................................................................................... 90
24.3.1 提升运算符.............................................................................................................................. 90
24.3.2 允许的用户定义运算符........................................................................................................... 91
24.3.3 运算符重载解析...................................................................................................................... 91
24.3.4 相等操作符和空...................................................................................................................... 91
24.3.5 is 运算符.................................................................................................................................. 91
24.3.6 as 运算符................................................................................................................................. 92
24.3.7 复合赋值................................................................................................................................. 92
24.3.8 bool? 类型................................................................................................................................ 92
24.3.9 空合并运算符.......................................................................................................................... 93
25. 其他功能......................................................................................................................................... 95
25.1 属性访问器的可访问性................................................................................................................ 95
25.1.1 访问器声明.............................................................................................................................. 95
25.1.2 访问器的使用.......................................................................................................................... 96
25.1.3 重写和接口实现...................................................................................................................... 97
25.2 静态类.......................................................................................................................................... 97
25.2.1 静态类声明.............................................................................................................................. 97
25.2.2 引用静态类类型...................................................................................................................... 98
25.3 命名空间别名限定符.................................................................................................................... 98
25.3.1 限定的别名成员.................................................................................................................... 100
25.3.2 别名的唯一性........................................................................................................................ 101
25.4 Extern 别名................................................................................................................................... 102
25.4.1 Extern 别名指令..................................................................................................................... 103
25.5 Pragma 指令................................................................................................................................. 104
25.5.1 Pragma warning...................................................................................................................... 105
25.6 默认值表达式............................................................................................................................. 105
25.7 条件属性类................................................................................................................................. 106
25.8 固定大小缓冲区.......................................................................................................................... 107
25.8.1 固定大小缓冲区的声明......................................................................................................... 107
25.8.2 表达式中的固定大小缓冲区.................................................................................................. 108
25.8.3 Fixed 语句.............................................................................................................................. 109
25.8.4 明确赋值检查........................................................................................................................ 109
简介
C# 2.0 引入了几项语言扩展,其中包括泛型 (Generic)、匿名方法 (Anonymous Method)、迭代器 (Iterator)、分部类型 (Partial Type) 和可空类型 (Nullable Type)。
· 泛型可以让类、结构、接口、委托和方法按它们存储和操作的数据的类型进行参数化。泛型很有用,因为它们能提供更强的编译时类型检查,减少数据类型之间的显式转换,以及装箱操作和运行时的类型检查。
· 在需要委托值的地方,匿名方法允许以“内联”方式编写代码块。匿名方法类似于 Lisp 编程语言中的 lambda 函数。C# 2.0 支持创建“closure”,其中的匿名方法可以访问外层局部变量和参数。
· 迭代器是执行递增计算并产生一系列值的方法。迭代器使类型可以简便地指定foreach 语句循环访问其元素的方式。
· 分部类型允许将类、结构和接口划分为多个部分,存储在不同的源文件中,以便于开发和维护。此外,分部类型允许将计算机生成的类型部分和用户编写的类型部分互相分开,以便更容易地扩充工具生成的代码。
· 可空类型表示可能未知的值。可空类型支持其基础类型的所有值以及一个附加的空状态。任何值类型均可作为可空类型的基础类型。可空类型支持与其基础类型相同的转换和运算符,另外还提供类似于 SQL 的空值传播。
本章将简要介绍这些新功能。之后,接下来的 5 章详细介绍这些功能的完整技术规范。最后一章介绍 C# 2.0 中引入的一些次要的扩展。
C# 2.0 中的语言扩展在设计上充分考虑并确保与现有代码的最大兼容性。例如,尽管 C# 2.0 在某些上下文中为单词 where、yield 和 partial 提供了特殊含义,但是这些单词仍可用作标识符。实际上,C# 2.0 没有添加新的关键字,因为这样的关键字可能与现有代码中的标识符冲突。
有关 C# 语言的最新信息以及如何为本文档提供反馈意见的说明,请访问“C# 语言主页”(http://msdn.microsoft.com/vcsharp/language)。
泛型可以让类、结构、接口、委托和方法按它们存储和操作的数据的类型进行参数化。使用过 Eiffel 或 Ada 泛型的用户或 C++ 模板的用户很快就能熟悉 C# 泛型,而且这些用户会发现 C# 泛型较之过去这些语言更加简便易用。
如果没有泛型,通用数据结构可通过使用类型 object 实现任何数据类型的存储。例如,下面是一个简单的 Stack 类,它将数据存储在一个 object 数组中,它的两个方法 Push 和 Pop 分别使用 object接受和返回数据:
public class Stack
{
object[] items;
int count;
public void Push(object item) {...}
public object Pop() {...}
}
虽然使用类型 object 使得 Stack 类非常灵活,但这种方式仍存在某些缺陷。例如,我们可以将任何类型的值(例如一个 Customer 实例)推入堆栈。但是,当从堆栈中检索某个值时,必须将 Pop 方法的结果显式强制转换回相应的类型,这样的代码编写起来颇为繁琐,而且在运行时执行的类型检查会造成额外的开销从而影响性能:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
再比如,当我们将一个值类型(例如 int)的值传递给 Push 方法时,则该值将自动被装箱。当以后检索该 int 时,必须使用显式类型强制转换将其取消装箱:
Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();
这样的装箱和取消装箱操作由于涉及动态内存分配和运行时类型检查而额外增加了性能开销。
上述 Stack 类还有一个潜在的问题就是我们无法对放到堆栈上的数据的种类施加限制。实际上,可能会发生这种情况:将一个 Customer 实例推入堆栈,而在检索到该实例之后却意外地将它强制转换为错误的类型:
Stack stack = new Stack();
stack.Push(new Customer());
string s = (string)stack.Pop();
虽然上面的代码错误使用了 Stack 类,但是从技术角度讲该代码可以视作是正确的,编译器不会报告编译时错误。这个问题在该代码被执行之前不会暴露出来,但在执行该代码时会引发 InvalidCastException。
显然,如果能够指定元素类型,Stack 类将能够从中受益。有了泛型,我们便可以做到这一点。
泛型提供了一种新的创建类型的机制,使用泛型创建的类型将带有类型形参 (type parameter)。下面的示例声明一个带有类型形参 T 的泛型 Stack 类。类型形参在 < 和 > 分隔符中指定并放置在类名后。Stack
public class Stack
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}
在使用泛型类 Stack
Stack
stack.Push(3);
int x = stack.Pop();
Stack
泛型提供了强类型机制,这意味着如果将一个 int 值推入 Customer 对象的堆栈将导致错误。正如 Stack
Stack
stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3); // Type mismatch error
int x = stack.Pop(); // Type mismatch error
泛型类型声明可以含有任意数目的类型形参。上面的 Stack
public class Dictionary
{
public void Add(K key, V value) {...}
public V this[K key] {...}
}
在使用上述 Dictionary
Dictionary
dict.Add("Peter", new Customer());
Customer c = dict["Peter"];
与非泛型类型类似,泛型类型的编译表示形式也是中间语言 (IL) 指令和元数据。当然,泛型类型的表示形式还要对类型形参的存在和使用进行编码。
在应用程序第一次创建构造泛型类型(例如 Stack
对于值类型,.NET 公共语言运行库为每次泛型类型实例化单独创建专用的本机代码副本。而对于所有的引用类型,则共享该本机代码的单个副本(因为在本机代码级别,引用不过是具有相同表示形式的
指针)。
通常,泛型类的作用并不仅仅是根据类型形参存储数据。泛型类常常需要调用对象上的方法,对象的类型由类型形参给出。例如,Dictionary
public class Dictionary
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...} // Error, no CompareTo method
...
}
}
由于为 K 指定的类型实参可以是任何类型,对于 key 参数,能够假设存在的成员仅限于类型 object 声明的成员,例如 Equals、GetHashCode 和 ToString;因此上面的示例将发生编译时错误。当然,我们可以将 key 参数强制转换为含有 CompareTo 方法的类型。例如,可以将 key 参数强制转换为 IComparable:
public class Dictionary
{
public void Add(K key, V value)
{
...
if (((IComparable)key).CompareTo(x) < 0) {...}
...
}
}
虽然这种解决方案可行,但它需要在运行时动态检查类型,因此会增加开销。另外它还将错误报告推迟到运行时,当键未实现 IComparable 时引发 InvalidCastException。
为了提供更强的编译时类型检查并减少类型强制转换,C# 允许为每个类型形参提供一个可选的约束 (constraint) 列表。类型形参约束指定了一个要求,类型必须满足该要求才能用作该类型形参的实参。约束使用单词 where 进行声明,后跟一个类型形参和一个冒号,再跟着一个逗号分隔的列表,列表项可以是类类型、接口类型甚或类型形参(还可以是特殊引用类型、值类型和构造函数约束)。
为了让 Dictionary
public class Dictionary
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
有了这个声明后,编译器将确保为 K 提供的任何类型实参均为一个实现了 IComparable 的类型。不仅如此,此约束声明还避免了在调用 CompareTo 方法之前显式将键参数强制转换为 IComparable;满足此类型形参约束的类型的所有成员都可作为该类型形参类型的值直接使用。
对于一个给定的类型形参,作为约束的接口和类型形参的数目不受限制,但只能有一个类。每个受约束的类型形参具有单独的 where 子句。在下面的示例中,类型形参 K 具有两个接口约束,而类型形参 E 具有一个类类型约束和一个构造函数约束:
public class EntityTable
where K: IComparable
where E: Entity, new()
{
public void Add(K key, E entity)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
在上面示例中,构造函数约束 new() 确保用作 E 的类型实参的类型具有无参数的公共构造函数,这样,泛型类便可以使用 new E() 创建该类型的实例。
需指出的是,类型形参约束应该谨慎使用。虽然它们提供更强的编译时类型检查,并在某些情况下改进了性能,但是它们也使泛型类型的使用受到限制。例如,泛型类 List
在有些情况下,并不是整个类都需要某个类型形参,而是仅在某个特定方法中需要。通常,在创建采用泛型类型作为形参的方法时会遇到这种情况。例如,在使用上述 Stack
void PushMultiple(Stack
foreach (int value in values) stack.Push(value);
}
然后,可以使用此方法将多个 int 值推入 Stack
Stack
PushMultiple(stack, 1, 2, 3, 4);
但是,上面的方法仅适用于特定的构造类型 Stack
所示:
void PushMultiple
foreach (T value in values) stack.Push(value);
}
使用此泛型方法,可以将多个项推入任何 Stack
Stack
PushMultiple
此泛型 PushMultiple 方法较之前一个版本更具可重用性,因为它适用于任何 Stack
Stack
PushMultiple(stack, 1, 2, 3, 4);
事件处理程序和其他回调方法通常仅通过委托机制进行调用,而从不直接进行调用。所以,我们目前还是只能将事件处理程序代码及回调代码置于特定的方法中,并显式为方法创建委托。而匿名方法 (anonymous method) 则不同,它允许与委托关联的代码以“内联”方式写入使用委托的位置,从而方便地将代码直接“绑定”到委托实例。除了这种便利之外,匿名方法还能够对包含它的函数成员的局部状态进行共享访问。而要使用具名方法实现同样的状态共享,需要将局部变量“提升”到手动编写的辅助类实例的字段中。
下面的示例显示一个包含列表框、文本框和按钮的简单输入窗体。单击该按钮时,一个包含文本框中所示文本的项将被添加到列表框中。
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender, EventArgs e) {
listBox.Items.Add(textBox.Text);
}
}
虽然在响应该按钮的 Click 事件时只执行了一条语句,但是必须将该语句提取到一个具有完整参数列表的单独方法中,并且必须手动创建一个引用该方法的 EventHandler 委托。使用匿名方法,该事件处理代码明显变得更简洁了:
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += delegate {
listBox.Items.Add(textBox.Text);
};
}
}
匿名方法由关键字 delegate、可选的参数列表和包含在 { 和 } 分隔符中的语句列表组成。上述示例中的匿名方法没有使用委托提供的参数,因此可以省略参数列表。若要获得对参数的访问,该匿名方法可以包括参数列表:
addButton.Click += delegate(object sender, EventArgs e) {
MessageBox.Show(((Button)sender).Text);
};
在上面的示例中,发生了从该匿名方法到 EventHandler 委托类型(Click 事件的类型)的隐式转换。能够进行该隐式转换是因为委托类型的参数列表和返回类型与匿名方法兼容。具体的兼容规则如下:
· 如果下列条件之一成立,则委托的参数列表与匿名方法兼容:
o 匿名方法没有参数列表并且委托没有 out 参数。
o 匿名方法包含的参数列表与委托的参数列表在数目、类型和修饰符方面都精确匹配。
· 如果下列条件之一成立,则委托的返回类型与匿名方法兼容:
o 委托的返回类型为 void,并且匿名方法没有 return 语句或只有无表达式的 return 语句。
o 委托的返回类型不为 void,并且与匿名方法中的所有 return 语句关联的表达式都可隐式转换为委托的返回类型。
委托的参数列表和返回类型都必须与匿名方法兼容才能进行从匿名方法到委托类型的隐式转换。
下面的示例使用匿名方法编写“内联”函数。匿名方法作为 Function 委托类型的参数来传递。
using System;
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
static double[] MultiplyAllBy(double[] a, double factor) {
return Apply(a, delegate(double x) { return x * factor; });
}
static void Main() {
double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, delegate(double x) { return x * x; });
double[] doubles = MultiplyAllBy(a, 2.0);
}
}
Apply 方法将给定的 Function 应用于 double[] 的各元素,并返回含有结果的 double[]。在 Main 方法中,传递给 Apply 的第二个参数是与 Function 委托类型兼容的匿名方法。此匿名方法完成一个简单的操作,即返回其实参的平方,因此该 Apply 调用的结果是包含 a 中各个值的平方的 double[]。
MultiplyAllBy 方法返回一个 double[],其中的值分别为实参数组 a 中的每个值乘以给定的 factor 后所得的结果。为了获得所需的结果,MultiplyAllBy 调用了 Apply 方法,并传递一个用来将实参 x 乘以 factor 的匿名方法。
其作用域包含某匿名方法的局部变量和参数称为该匿名方法的外层变量 (outer variable)。在 MultiplyAllBy 方法中,a 和 factor 是传递给 Apply 的匿名方法的外层变量,并且由于该匿名方法引用 factor,我们称 factor 被该匿名方法捕获 (captured)。通常,局部变量的生存期仅限于该变量所关联的代码块或语句的执行期间。但是,被捕获的外层变量的生存期将至少延长至引用匿名方法的委托可以被垃圾回收为止。
正如前一节所述,匿名方法可隐式转换为与之兼容的委托类型。C# 2.0 允许对方法组进行这种相同类型的转换,这样,几乎在所有情况下都可以省略显式的委托实例化。例如,语句
addButton.Click += new EventHandler(AddClick);
Apply(a, new Function(Math.Sin));
可改写为
addButton.Click += AddClick;
Apply(a, Math.Sin);
当使用较短的形式时,编译器自动推断要实例化的委托类型,而效果与使用较长的形式是一样的。
C# foreach 语句用于循环访问可枚举 (enumerable) 集合的元素。为了成为可枚举的类型,集合必须具有返回枚举器 (enumerator) 的无参数的 GetEnumerator 方法。一般而言,枚举器不易实现,但是通过使用迭代器可以显著简化该任务。
迭代器 (iterator) 是一个产生 (yield) 有序值序列的语句块。迭代器与普通语句块的区别在于迭代器存在一个或多个 yield 语句:
· yield return 语句产生迭代的下一个值。
· yield break 语句指示迭代完成。
只要函数成员的返回类型是枚举器接口 (enumerator interface) 之一或可枚举接口 (enumerable interface) 之一,迭代器就可用作该函数成员的函数体:
· 枚举器接口为 System.Collections.IEnumerator 和从 System.Collections.Generic.IEnumerator
· 可枚举接口为 System.Collections.IEnumerable 和从 System.Collections.Generic.IEnumerable
迭代器并非一种成员,而是一种实现函数成员的手段,了解这一点很重要。通过迭代器实现的成员能够被其他可能使用也可能不使用迭代器实现的成员重写或重载。
下面的 Stack
using System.Collections.Generic;
public class Stack
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
}
GetEnumerator 方法的存在使得 Stack
using System;
class Test
{
static void Main() {
Stack
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack) Console.Write("{0} ", i);
Console.WriteLine();
}
}
该示例的输出为:
9 8 7 6 5 4 3 2 1 0
foreach 语句隐式调用集合的无参数 GetEnumerator 方法获得枚举器。一个集合只能定义一个这样的无参数 GetEnumerator 方法,但是可以有多种枚举方式以及多种通过参数控制枚举的方式。在这种情况下,集合可以使用迭代器实现多个返回可枚举接口类型之一的属性或方法。例如,Stack
using System.Collections.Generic;
public class Stack
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
public IEnumerable
get {
return this;
}
}
public IEnumerable
get {
for (int i = 0; i < count; i++) {
yield return items[i];
}
}
}
}
TopToBottom 属性的 get 访问器直接返回 this,因为该堆栈本身是可枚举的。BottomToTop 属性返回一个使用 C# 迭代器实现的可枚举接口类型。下面的示例显示如何使用这两个属性以任一顺序枚举堆栈元素:
using System;
class Test
{
static void Main() {
Stack
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack.TopToBottom) Console.Write("{0} ", i);
Console.WriteLine();
foreach (int i in stack.BottomToTop) Console.Write("{0} ", i);
Console.WriteLine();
}
}
当然,这些属性也可以在 foreach 语句之外使用。下面的示例将调用这些属性的结果传递给一个单独的 Print 方法。该示例还演示了一个用作 FromToBy 方法(该方法带有参数)的方法体的迭代器:
using System;
using System.Collections.Generic;
class Test
{
static void Print(IEnumerable
foreach (int i in collection) Console.Write("{0} ", i);
Console.WriteLine();
}
static IEnumerable
for (int i = from; i <= to; i += by) {
yield return i;
}
}
static void Main() {
Stack
for (int i = 0; i < 10; i++) stack.Push(i);
Print(stack.TopToBottom);
Print(stack.BottomToTop);
Print(FromToBy(10, 20, 2));
}
}
该示例的输出为:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
10 12 14 16 18 20
,即一个不带参数并返回一个枚举器接口类型的 GetEnumerator 方法。可枚举接口起到“枚举器工厂”的作用。如果正确实现了可枚举接口,每次调用它们的 GetEnumerator 方法时,都会生成一个独立的枚举器。假设在两次调用 GetEnumerator 期间可枚举接口类型的内部状态没有改变,则所返回的两个枚举器应以相同的顺序产生相同的枚举值集合。即使枚举器的生存期重叠,这个结论也应该有效,如下面的代码示例所示:
using System;
using System.Collections.Generic;
class Test
{
static IEnumerable
while (from <= to) yield return from++;
}
static void Main() {
IEnumerable
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}
上面的代码可输出整数 1 至 10 的简单乘法表。注意,FromTo 方法仅被调用一次来生成可枚举的 e。但是,e.GetEnumerator() 被调用多次(由 foreach 语句调用)以生成多个等效的枚举器。这些枚举器全都封装了在 FromTo 的声明中指定的迭代器代码。注意该迭代器代码将修改 from 参数。然而,这些枚举器独立工作,因为每个枚举器都会获得它自己的 from 和 to 参数的副本。枚举器之间的过渡状态的共享是若干常见细微缺陷之一,在实现可枚举接口和枚举器时应该避免。C# 迭代器旨在帮助避免这些问题,并以简单、直观的方法实现可靠的可枚举接口和枚举器。
虽然在单个文件中维护某个类型的所有源代码是个很好的编程习惯,但是有时一个类型会变得非常大,在这种情况下,这种做法反而成为了一种不切实际的限制。此外,程序员经常使用源代码生成器产生应用程序的初始结构,然后修改结果代码。遗憾的是,当将来某个时候再次发出源代码时,现有修改将被改写。
分部类型 (Partial type) 允许将类、结构和接口划分为多个部分,存储在不同的源文件中,以便于开发和维护。此外,分部类型允许将计算机生成的类型部分和用户编写的类型部分互相分开,以便更容易地扩充工具生成的代码。
当通过多个部分来定义类型时,将使用新增的类型修饰符 partial。下面是一个分为两部分来实现的分部类示例。这里,第一部分是通过数据库映射工具由计算机生成的,而第二部分是手动编写的,所以,可将这两个部分分别存储在不同的源文件中:
public partial class Customer
{
private int id;
private string name;
private string address;
private List
public Customer() {
...
}
}
public partial class Customer
{
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
当将上述两个部分一起编译时,结果代码与将该类编写为单个单元时相同:
public class Customer
{
private int id;
private string name;
private string address;
private List
public Customer() {
...
}
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
分部类型的所有部分必须一起编译,以使这些部分可在编译时被合并。特别指出的是,分部类型不允许对已经编译的类型进行扩展。
在与数据库交互时,支持所有类型(包括值类型)的可空值是很重要的,而一直以来通用编程语言很少或没有提供这方面的支持。在没有直接语言支持的情况下,虽然也存在许多用于处理空值和值类型的方法,但都存在缺点。例如,一种方法是使用“特殊”值(例如将 −1 用于整数)指示空值,但是这种方法只有在能够确定未使用的值的情况下有效。另一种方法是在单独的字段或变量中维护布尔空值指示符,但是这种方法不是很适合参数和返回值。第三种方法是使用一组用户定义的可空类型,但是这仅适用于属于闭集的类型集合。C# 的可空类型 (nullable type) 通过为所有值类型的可空形式提供完整和集成的支持,解决了这个由来已久的问题。
可空类型是使用 类型修饰符来构造的。例如,int? 是预定义类型 int 的可空形式。可空类型的基础类型必须是非可空的值类型。
可空类型是一个组合了基础类型的值和布尔空值指示符的结构。可空类型的实例具有两个公共只读属性:bool 类型的 HasValue 和可空类型的基础类型的 Value。HasValue 对所有非空实例都为 true,对空实例为 false。当 HasValue 为 true 时,Value 属性返回所包含的值。当 HasValue 为 false 时,尝试访问 Value 属性将引发异常。
可从任何非可空值类型隐式转换到该类型的可空形式。此外,还可从 null 文本隐式转换到任何可空类型。在下面的示例中
int? x = 123;
int? y = null;
if (x.HasValue) Console.WriteLine(x.Value);
if (y.HasValue) Console.WriteLine(y.Value);
int 值 123 和 null 文本被隐式转换为可空类型 int?。该示例针对 x 输出 123,但是第二个 Console.WriteLine 未执行,因为 y.HasValue 为 false。
可空转换 (Nullable conversion) 和提升转换 (lifted conversion) 允许对非可空值类型进行操作的预定义转换和用户定义转换也能够用于这些类型的可空形式。同样,提升运算符 (lifted operator) 允许用于非可空值类型的预定义运算符和用户定义运算符也能够用于这些类型的可空形式。
对于从非可空值类型 S 到非可空值类型 T 的每个预定义转换,将自动存在从 S? 到 T? 的预定义可空转换。这种可空转换是基础转换的一种空传播 (null propagating) 形式:它将空的源值直接转换为空的目标值,但是对其他值则执行基础非空转换。此外还进一步提供了从 S 到 T? 和从 S? 到 T 的可空转换,后一种转换作为在源值为空时将引发异常的显式转换。
下面是一些可空转换示例。
int i = 123;
int? x = i; // int --> int?
double? y = x; // int? --> double?
int? z = (int?)y; // double? --> int?
int j = (int)z; // int? --> int
当源和目标类型均为非可空值类型时,用户定义的转换运算符将具有提升形式。源和目标类型将添加一个 修饰符以创建提升形式。与预定义的可空转换类似,提升转换运算符也可传播空值。
当操作数类型和结果类型全都为非可空值类型时,非比较运算符具有提升形式。对于非比较运算符,每个操作数类型和结果类型将添加一个 修饰符以创建提升形式。例如,接受两个 int 操作数并返回一个 int 的预定义 + 运算符的提升形式是接受两个 int? 操作数并返回一个 int? 的运算符。与提升转换类似,提升非比较运算符也进行空传播:如果提升运算符的任一操作数为空,则结果为空值。
下面的示例使用 + 提升运算符将两个 int? 值相加:
int? x = GetNullableInt();
int? y = GetNullableInt();
int? z = x + y;
对 z 的赋值实际上等价于:
int? z = x.HasValue && y.HasValue ? x.Value + y.Value : (int?)null;
由于存在从非可空值类型到其可空形式的隐式转换,当只有一个操作数是可空类型时,提升运算符同样适用。下面的示例使用与上述示例相同的提升后的 + 运算符:
int? x = GetNullableInt();
int? y = x + 1;
如果 x 为空,则 y 被赋值为空。否则,y 被赋以 x 加一的值。
C# 的可空转换、提升转换和提升非比较运算符的空传播语义非常类似于 SQL 中的对应转换和运算符。但是,C# 的提升比较运算符产生标准的布尔结果而不是引入 SQL 的三值布尔逻辑。
当操作数类型均为非可空值类型并且结果类型为 bool 时,比较运算符(==、!=、<、>、<=、>=)具有提升形式。比较运算符的提升形式是通过向每个操作数类型添加一个 修饰符(但是不添加到结果类型)构成的。== 和 != 运算符的提升形式将两个空值视为相等,并且空值不等于非空值。如果一个为空或者两个操作数都为空,<、>、<= 和 >= 运算符的提升形式返回 false。
当 == 或 != 运算符的操作数之一为 null 文本时,另一个操作数可以是任何可空类型,而不管基础值类型是否实际声明了该运算符。在没有为运算符 == 或 != 提供实现的情况下,操作数的 HasValue 属性的检查将被替换。此规则的效果在于,类似如下的语句
if (x == null) Console.WriteLine("x is null");
if (x != null) Console.WriteLine("x is non-null");
对于任何可空类型或引用类型的 x 都是允许的,从而为可为空值的所有类型执行空值检查提供一种公共的方法。
另外还提供了空合并运算符 (null coalescing operator) 。如果 a 为非空,则 a b 的结果为 a;否则结果为 b。从效果看,b 提供了要在 a 为空时使用的值。
当 a 为可空类型而 b 为非可空类型时,只要操作数类型之间存在适当的隐式转换,a b 将返回非可空值。在下面的示例中
int? x = GetNullableInt();
int? y = GetNullableInt();
int? z = x ?? y;
int i = z ?? -1;
x y 的类型为 int?,但是 z -1 的类型为 int。后一种运算特别方便,因为它从类型移除 ,同时还提供要在为空值时使用的默认值。
空合并运算符也适用于引用类型。下面的示例
string s = GetStringValue();
Console.WriteLine(s ?? "Unspecified");
输出 s 的值,或在 s 为空值时输出 Unspecified。
泛型类声明是一种类的声明,它需要提供类型实参才能构成实际类型。
类声明可以有选择地定义类型形参:
class-declaration:
attributesopt class-modifiersopt class identifier type-parameter-listopt class-baseopt
type-parameter-constraints-clausesopt class-body ;opt
只有提供了一个 type-parameter-list,才可以为这个类声明提供 type-parameter-constraints-clauses(第 20.7 节)。
提供了 type-parameter-list 的类声明是一个泛型类声明。此外,任何嵌套在泛型类声明或泛型结构声明中的类本身就是一个泛型类声明,因为必须为包含类型提供类型形参才能创建构造类型。
除了明确指出的地方外,泛型类声明与非泛型类声明遵循相同的规则。泛型类声明可嵌套在非泛型类声明中。
使用构造类型 (constructed type)(第 20.4 节)引用泛型类。给定下面的泛型类声明
class List
List