这篇文章是翻译的微软的技术文章 .供学习c#的朋友参考,请勿用于商业目的。 http://msdn.microsoft.com/vcsharp/team/language/default.aspx
19.C#2.0 介绍
C#2.0引入了几项语言扩展,其中最重要的是泛型、匿名方法、迭代器和不完整类型(partial type)。
- 泛型可以让类、结构、接口、委托和方法,通过他们所存储和操纵的数据的类型被参数化。泛型是很有用的,因为他们提供了更强的编译时类型检查,减少了数据类型之间的显式转换,以及装箱操作和运行时类型检查。
- 匿名方法可以让代码块以内联的方式潜入到期望委托值的地方。匿名方法与 Lisp 编程语言中的λ函数( lambda function )相似。 C#2.0 支持“ closures ”的创建,在其中匿名方法可以访问相关局部变量和参数。
- 迭代器是可以递增计算和产生值的方法。迭代器让类型指定 foreach 语句如何迭代它的所有元素,变得很容易。
- 不完整类型可以让类、结构和接口被拆分成多个部分存储在不同的源文件中,这更利于开发和维护。此外,不完整类型允许某些类型的机器生成的部分与用户编写的部分之间的分离,因此增加由工具产生的代码很容易。
本章将介绍这些新特征。介绍完之后,接下来的四章提供了这些特征的完整的技术规范。
C#2.0的语言扩展主要被设计用于确保与现存的代码之间最大的兼容性。例如,尽管C#2.0对于 where 、 yield 和 partial 这些词在特定上下文中赋予了特别的意义,但这些词仍然可被用作标识符。实际上, C# 2.0没有增加任何可能与现有代码中的标识符冲突的关键字。
19.1 泛型
泛型可以让类、结构、接口、委托和方法,通过他们所存储和操纵的数据的类型被参数化。 C#泛型对于使用Eiffel或Ada的泛型的用户,或者对于C++模板的用户来说是很熟悉的;但他们将不用再去忍受后者的众多的复杂性。
19.1.1 为什么使用泛型
没有泛型的话,通用目的的数据结构可以采用 object 类型存储任何类型的数据。例如,下面的 Stack 类在一个 object 数组中存储数据,而它的两个方法, Push 和 Pop 相应地使用 object 接收和返回数据。
public class Stack
{
object[] items;
int count;
public void Push(object item){…}
public object Pop(){…}
}
尽管使用类型 object 可以使得 Stack 类更加灵活,但这样做也并不是没有缺点。例如,你可以将一个任何类型的值,诸如, Customer 的一个实例压入( Push )堆栈。但当你取回一个值时, 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 类具有能够指定其元素的类型能力,那么很显然它能从这种能力得到好处。使用泛型,这将会变成可能。
19.1.2 创建和使用泛型
泛型为创建具有类型参数( type paramete r)的类型提供了工具。下面的例子声明了一个带有类型参数T的泛型 Stack 类。类型参数在类名字之后的“ <“和“>”分界符中指定。这里没有 object 与别的类型之间的相互转换, Stack
1<t> 的实例接受它们被创建时的类型,并且存储那个类型的数据而没有转换它。类型参数 T充当一个占位符,直到使用的时候才指定一个实际的类型。注意,T被用作内部 items 数组的元素类型、 Pus h方法参数的类型和 Pop 方法的返回值类型。
2
3Public class Stack<t>
4
5{
6
7T[] items;
8
9int count;
10
11public void Push(T item){…}
12
13public T Pop(){…}
14
15}
16
17当泛型类 Stack<t> 被使用时, T所代替的实际类型将被指定。在下面的例子中, int 将被作为T的类型参数而给出。
18
19Stack<int> stack = new Stack<int>();
20
21Stack.Push(3);
22
23int x = stack.Pop();
24
25Stack<int> 类型被称为构造类型( constructed type )。在 Stack<int> 类型中, T的每次出现都被使用类型参数int代替。当Stack<int>的实例被创建时,items数组的本地存储就是一个 int[] 而不是 object[] ,与非泛型 Stack 相比,它提供了更高的存储效率。同样地,在 int 值上的 Stack<int> 操作的 Push 和 Pop 方法,将会使得压入其他类型的值到堆栈中出现一个编译时错误,并且当取回值的时候也不需要转换回它们原始的类型。
26
27泛型提供了强类型,意义例如压入一个 int 到 Customer 对象堆栈将会出现错误。就好像 Stack<int> 被限制只能在 int 值上操作,同样 Stack<customer> 也被限制用于 Customer 对象。
28
29对于下面的例子,编译器将会在最后两行报告错误。
30
31
32
33
34Stack<customer> stack = new Stack<customer>();
35
36Stack.Push(new Customer());
37
38Customer c = stack.Pop();
39
40stack.Push(3); // 类型不匹配错误
41
42int x = stack.Pop(); // 类型不匹配错误
43
44泛型类型声明可以有任意数量的类型参数。先前的 Stack<t> 例子 只有一个类型参数,但一个通用的 Dictionary 类可能有两个类型参数,一个用于键 ( key )的类型,另一个用于值( value )的类型。
45
46public class Dictionary<k ,="" v="">
47
48{
49
50public void Add(K key , V value){…}
51
52public V this[K key]{…}
53
54}
55
56当 Dictionary<k ,="" v=""> 被使用时,必须提供两个类型参数。
57
58Dictionary<string ,="" customer=""> dict = new Dictionary<string ,="" customer="">();
59
60Dict.Add(“Peter”, new Customer());
61
62Custeomer c = dict[“Perter”];
63
64### 19.1.3 泛型类型实例化
65
66与非泛型类型相似,被编译过的泛型类型也是由中间语言 [Intermediate Language(IL)]指令和元数据表示。泛型类型的表示当然也对类型参数的存在和使用进行了编码。
67
68当应用程序首次创建一个构造泛型类型的实例时,例如, Stack<int> , .NET公共语言运行时的实时编译器( JIT )将在进程中把泛型 IL和元数据转换为本地代码,并且将类型参数替换为实际的类型。对于那个构造泛型类型的后续引用将会使用相同的本机代码。从一个泛型类型创建一个特定构造类型的过程,称为 ** 泛型类型实例化( ** ** generic type instantiation ** ** )。 ** ** **
69
70.NET公共语言运行时使用值类型为每个泛型类型实例创建了一个本地代码的特定拷贝,但对于所有的引用类型它将共享那份本地代码的单一拷贝(因为,在本地代码级别,引用只是带有相同表示的指针)。
71
72### 19.1.4 约束
73
74一般来讲,泛型类不限于只是根据类型参数存储值。泛型类经常可能在给定类型参数的类型的对象上调用方法。例如, Dictionary<k ,="" v=""> 类中的 Add方法可能需要使用 CompareTo 方法比较键值。
75
76
77
78
79public class Dictionary<k ,="" v="">
80
81{
82
83public void Add(K key , V value)
84
85{
86
87…
88
89if(key.CompareTo(x)<0){…}// 错误,没有 CompareTo 方法
90
91…
92
93}
94
95}
96
97因为为 K所指定的类型参数可能是任何类型,可以假定 key 参数存在的唯一成员,就是那些被声明为 object 类型的,例如, Equals , GetHashCode 和 ToString ;因此,在先前例子中将会出现编译时错误。当然,你可以将 key参数强制转换到一个包含 CompareTo 方法的类型。例如, key 参数可能被强制转换到 IComparable 接口。
98
99public class Dictionary<k ,="" v="">
100
101{
102
103public void Add(K key , V value)
104
105{
106
107…
108
109if(((IComparable)key).CompareTo(x)<0){…}
110
111…
112
113}
114
115}
116
117尽管这种解决办法有效,但它需要在运行时的动态类型检查,这也增加了开销。更糟糕的是,它将错误报告推迟到了运行时,如果键( key )没有实现 IComparable 接口将会抛出 InvalidCastException 异常。
118
119为了提供更强的编译时类型检查,并减少类型强制转换, C#允许为每个类型参数提供一个 ** 约束( ** ** constraint ** ** ) ** 的可选的列表。类型参数约束指定了类型必须履行的一种需求,其目的是为了为类型参数被用作实参( argument )。约束使用单词 where 声明,随后是类型参数的名字,接着是类或接口类型的列表,和可选的构造函数约束 new() 。
120
121public class Dictionary<k, v=""> where K :IComparable
122
123{
124
125public void Add(K key , V value)
126
127{
128
129…
130
131if(key.CompareTo(x)<0){…}
132
133…
134
135}
136
137}
138
139
140
141
142给定这个声明,编译器将会确保 K的任何类型实参是实现了 IComparable 接口的类型。
143
144并且,在调用 CompareTo 方法之前也不再需要对 key 参数进行显式地强制转换。为类型参数作为一个约束而给出的类型的所有成员,对于类型参数类型的值时直接有效的。
145
146对于一个给定的类型参数,你可以指定任意数量的接口作为约束,但只能有一个类。每个约束的类型参数有一个单独的 where 语句。在下面的例子中,类型参数K有两个接口约束,类型参数e有一个类约束和一个构造函数约束。
147
148public class EntityTable<k, e="">
149
150where K:IComparable<k>,IPersisable
151
152where E:Entity, new()
153
154{
155
156public void Add(K key , E entity)
157
158{
159
160…
161
162if(key.CompareTo(x)<0){…}
163
164…
165
166}
167
168}
169
170在前面的例子中,构造函数约束 new() ,确保为 E用作类型参数的类型具有一个公有的、无参数构造函数,并且它允许泛型类使用 new E() 创建该类型的实例。
171
172类型参数约束应该很小心的使用。尽管它们提供了更强的编译时类型检查,在某些情况下增强了性能,但它们也限制了泛型类型的可能的用法。例如,泛型类 List<t> 可能约束 T实现 IComparable 接口,由此它的 Sort 方法将可以比较项的大小。然而,这么做却使得没有实现 IComparable 接口的类型不能使用 List<t> ,即使是在这些情形下, Sort 方法根本就没有被调用过。</t></t></k></k,></k,></k></k></k></int></string></string></k></k></t></customer></customer></customer></int></int></int></int></int></int></int></t></t></t>