** 来源: Mircrosoft.NET 2.0 Beta1 SDK **
** 日期: 2004-11-2 **
泛型( generic )是 C# 语言 2.0 和通用语言运行时( CLR )的一个新特性。泛型为 .NET 框架引入了类型参数( type parameters )的概念。类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。这意味着使用泛型的类型参数 T ,写一个类 MyList
1<t> ,客户代码可以这样调用: MyList<int> , MyList<string> 或 MyList<myclass> 。这避免了运行时类型转换或装箱操作的代价和风险。
2
3** 目录 **
4
5**C#** ** 中的泛型 ** . 1
6
7** 一、泛型概述 ** . 2
8
9** 二、泛型的优点 ** . 5
10
11** 三、泛型类型参数 ** . 7
12
13** 四、类型参数的约束 ** . 8
14
15** 五、泛型类 ** . 11
16
17** 六、泛型接口 ** . 13
18
19** 七、泛型方法 ** . 19
20
21** 八、泛型委托 ** . 21
22
23** 九、泛 型代码中的default ** ** 关键字 ** . 23
24
25** 十、C++ ** ** 模板和C# ** ** 泛型的区别 ** . 24
26
27** 十一 ** ** 、运行时中的泛型 ** . 25
28
29** 十二 ** ** 、基础类库中的泛型 ** . 27
30
31**
32**
33
34** 一、泛型概述 ** ** **
35
36泛型类和泛型方法兼复用性、类型安全和高效率于一身,是与之对应的非泛型的类和方法所不及。泛型广泛用于容器( collections )和对容器操作的方法中。 .NET 框架 2.0 的类库提供一个新的命名空间 System.Collections.Generic ,其中包含了一些新的基于泛型的容器类。要查找新的泛型容器类( collection classes )的示例代码,请参见基础类库中的泛型。当然,你也可以创建自己的泛型类和方法,以提供你自己的泛化的方案和设计模式,这是类型安全且高效的。下面的示例代码以一个简单的泛型链表类作为示范。 (多数情况下,推荐使用由 .NET 框架类库提供的 List<t> 类,而不是创建自己的表。 )类型参数 _ T _ 在多处使用, 具体类型通常在 这些地方 来指明表中元素的类型。 类型参数 T 有以下几种用法:
37
38l 在 AddHead 方法中, 作为方法参数的类型。
39
40l 在公共 方法 GetNext 中,以及嵌套类 Node 的 Data 属性中作为返回值的类型。
41
42l 在嵌套类中,作为私有成员 data 的类型。
43
44注意一点, T 对嵌套的类 Node 也是有效的。当用一个具体类来实现 MyList<t> 时——如 MyList<int> ——每个出现过的 T 都要用 int 代替。
45
46using System;
47
48using System.Collections.Generic;
49
50public class MyList<t> //type parameter T in angle brackets
51
52{
53
54private Node head;
55
56// The nested type is also generic on T.
57
58private class Node
59
60{
61
62private Node next;
63
64//T as private member data type:
65
66private T data;
67
68//T used in non-generic constructor:
69
70public Node(T t)
71
72{
73
74next = null;
75
76data = t;
77
78}
79
80public Node Next
81
82{
83
84get { return next; }
85
86set { next = value; }
87
88}
89
90//T as return type of property:
91
92public T Data
93
94{
95
96get { return data; }
97
98set { data = value; }
99
100}
101
102}
103
104public MyList()
105
106{
107
108head = null;
109
110}
111
112//T as method parameter type:
113
114public void AddHead(T t)
115
116{
117
118Node n = new Node(t);
119
120n.Next = head;
121
122head = n;
123
124}
125
126public IEnumerator<t> GetEnumerator()
127
128{
129
130Node current = head;
131
132while (current != null)
133
134{
135
136yield return current.Data;
137
138current = current.Next;
139
140}
141
142}
143
144}
145
146下面的示例代码演示了客户代码如何使用泛型类 MyList<t> ,来创建一个整数表。通过简单地改变参数的类型,很容易改写下面的代码,以创建字符串或其他自定义类型的表。
147
148class Program
149
150{
151
152static void Main (string[] args)
153
154{
155
156//int is the type argument.
157
158MyList<int> list = new MyList<int>();
159
160for (int x = 0; x < 10; x++)
161
162list.AddHead(x);
163
164foreach (int i in list)
165
166{
167
168Console.WriteLine(i);
169
170}
171
172Console.WriteLine("Done");
173
174}
175
176}
177
178
179
180
181** 二、泛型的优点 ** ** **
182
183针对早期版本的通用语言运行时和 C# 语言的局限,泛型提供了一个解决方案。以前类型的泛化( generalization )是靠类型与全局基类 System.Object 的相互转换来实现。 .NET 框架基础类库的 ArrayList 容器类,就是这种局限的一个例子。 ArrayList 是一个很方便的容器类,使用中无需更改就可以存储任何引用类型或值类型。
184
185//The .NET Framework 1.1 way of creating a list
186
187ArrayList list1 = new ArrayList();
188
189list1.Add(3);
190
191list1.Add(105);
192
193//...
194
195ArrayList list2 = new ArrayList();
196
197list2.Add(“It is raining in Redmond .”);
198
199list2.Add("It is snowing in the mountains.");
200
201//...
202
203但是这种便利是有代价的,这需要把任何一个加入 ArrayList 的引用类型或值类型都隐式地向上转换成 System.Object 。如果这些元素是值类型,那么当加入到列表中时,它们必须被装箱;当 重新取回 它们时,要拆箱。类型转换和装箱、拆箱的操作都降低了性能;在必须迭代( iterate )大容器的情况下,装箱和拆箱的影响可能十分显著。
204
205另一个局限是缺乏编译时的类型检查,当一个 ArrayList 把任何类型都转换为 Object ,就无法在编译时预防客户代码类似这样的操作:
206
207ArrayList list = new ArrayList();
208
209//Okay.
210
211list.Add(3);
212
213//Okay, but did you really want to do this?
214
215list.Add(.“It is raining in Redmond .”);
216
217int t = 0;
218
219//This causes an InvalidCastException to be returned.
220
221foreach(int x in list)
222
223{
224
225t += x;
226
227}
228
229虽然这样完全合法,并且有时是有意这样创建一个包含不同类型元素的容器,但是把 string 和 int 变量放在一个 ArrayList 中,几乎是在制造错误,而这个错误直到运行的时候才会被发现。
230
231在 1.0 版和 1.1 版的 C# 语言中,你只有通过编写自己的特定类型容器,才能避免 .NET 框架类库的容器类中泛化代码( generalized code )的危险。当然,因为这样的类无法被其他的数据类型复用,也就失去泛型的优点,你必须为每个需要存储的类型重写该类。
232
233ArrayList 和其他相似的类真正需要的是一种途径,能让客户代码在实例化之前指定所需的特定数据类型。这样就不需要向上类型转换为 Object ,而且编译器可以同时进行类型检查。换句话说, ArrayList 需要一个类型参数。这正是泛型所提供的。在 System.Collections.Generic 命名空间中的泛型 List<t> 容器里,同样是把元素加入容器的操作,类似这样:
234
235The .NET Framework 2.0 way of creating a list
236
237List<int> list1 = new List<int>();
238
239//No boxing, no casting:
240
241list1.Add(3);
242
243//Compile-time error:
244
245list1.Add("It is raining in Redmond .");
246
247与 ArrayList 相比, 在客户代码中唯一增加的 List<t> 语法是声明和实例化中的类型参数。代码略微复杂的回报是,你创建的表不仅比 ArrayList 更安全,而且明显地更加快速,尤其当表中的元素是值类型的时候。
248
249
250
251
252** 三、泛型类型参数 ** ** **
253
254在泛型类型或泛型方法的定义中,类型参数是一个占位符( placeholder ),通常为一个大写字母,如 T 。在客户代码声明、实例化该类型的变量时,把 T 替换为客户代码所指定的数据类型。泛型类,如泛型概述中给出的 MyList<t> 类,不能用作 as-is ,原因在于它不是一个真正的类型,而更像是一个类型的蓝图。要使用 MyList<t> ,客户代码必须在尖括号内指定一个类型参数,来声明并实例化一个已构造类型( constructed type )。这个特定类的类型参数可以是编译器识别的任何类型。可以创建任意数量的已构造类型实例,每个使用不同的类型参数,如下:
255
256MyList<myclass> list1 = new MyList<myclass>();
257
258MyList<float> list2 = new MyList<float>();
259
260MyList<somestruct> list3 = new MyList<somestruct>();
261
262在这些 MyList<t> 的实例中,类中出现的每个 T 都将在运行的时候被类型参数所取代。依靠这样的替换,我们仅用定义类的代码,就创建了三个独立的类型安全且高效的对象。有关 CLR 执行替换的详细信息,请参见运行时中的泛型。
263
264
265
266
267** 四、类型参数的约束 ** ** **
268
269若要检查表中的一个元素,以确定它是否合法或是否可以与其他元素相比较,那么编译器必须保证:客户代码中可能出现的所有类型参数,都要支持所需调用的操作或方法。这种保证是通过在泛型类的定义中,应用一个或多个约束而得到的。一个约束类型是一种基类约束,它通知编译器,只有这个类型的对象或从这个类型派生的对象,可被用作类型参数。一旦编译器得到这样的保证,它就允许在泛型类中调用这个类型的方法。上下文关键字 where 用以实现约束。下面的示例代码说明了应用基类约束,为 MyList<t> 类增加功能。
270
271public class Employee
272
273{
274
275public class Employee
276
277{
278
279private string name;
280
281private int id;
282
283public Employee(string s, int i)
284
285{
286
287name = s;
288
289id = i;
290
291}
292
293public string Name
294
295{
296
297get { return name; }
298
299set { name = value; }
300
301}
302
303public int ID
304
305{
306
307get { return id; }
308
309set { id = value; }
310
311}
312
313}
314
315}
316
317class MyList<t> where T: Employee
318
319{
320
321//Rest of class as before.
322
323public T FindFirstOccurrence(string s)
324
325{
326
327T t = null;
328
329Reset();
330
331while (HasItems())
332
333{
334
335if (current != null)
336
337{
338
339//The constraint enables this:
340
341if (current.Data.Name == s)
342
343{
344
345t = current.Data;
346
347break;
348
349<SPAN lan</t></t></t></somestruct></somestruct></float></float></myclass></myclass></t></t></t></int></int></t></int></int></t></t></t></int></t></t></myclass></string></int></t>