五、增加泛型
前面我们通过一个ArrayList类实现了IList接口,通过代码可以知道,ArrayList采用一个object[]类型的数组字段来存储对象引用。这种方式存在两个问题:
- Object是引用类型,所以一旦我们向ArrayList集合中存储任何值类型对象,则会发生一次“装箱”操作,.net会将值类型对象转化为引用类型对象;其后每次访问集合中的该对象时,又可能会引起一次“拆箱”操作;
- 由于使用Object类型作为对象的引用类型,所以ArrayList无法限制存储对象引用的类型。
看一个例子:
1 | class Program { |
2 | static void Main(string[] args) { |
3 | |
4 | // 声明向量集合对象 |
5 | ArrayList lst = new ArrayList(); |
6 | |
7 | // 当调用Add方法时, 值类型(int)会自动的"装箱"为引用类型(object) |
8 | lst.Add(100); |
9 | |
10 | // 当调用索引器时, 返回的是一个object类型的对象引用 |
11 | object o = lst[0]; |
12 | |
13 | // 要恢复存储前的类型, 这里还需要一次"拆箱"操作 |
14 | int value = (int)o; |
15 | |
16 | Console.WriteLine(value); |
17 | |
18 | // 这里, 先进行了一次"拆箱"操作, 将lst[0]转化为int类型 |
19 | // 接着进行了一次装箱操作 |
20 | lst[0] = (int)lst[0] + 200; |
21 | Console.WriteLine(lst[0]); |
22 | |
23 | // 对于ArrayList, 可以存放任意类型的对象引用 |
24 | lst.Add("Hello"); |
25 | |
26 | foreach (object v in lst) { |
27 | // 这里只能访问object类型的ToString方法 |
28 | // 并无法知道object类型的v变量到底是什么类型 |
29 | Console.WriteLine(v); |
30 | } |
31 | } |
32 | } |
通过上述例子,可以清楚的反映出上面提到的两个问题。
.net Framework添加的泛型特性就是为了解决上述问题的。为了支持泛型,我们需要额外的实现一个接口:List<E>接口。
泛型的代码理解起来颇为抽象,下面是代码,童鞋们尽量理解。
1 | using System; |
2 | using System.Collections; |
3 | using System.Collections.Generic; |
4 | |
5 | namespace Edu.Study.Collections.List { |
6 | |
7 | /// <summary> |
8 | /// 定义向量集合类, 实现IList接口 |
9 | /// </summary> |
10 | public class List<E> : IList, IList<E> { |
11 | |
12 | /// <summary> |
13 | /// 泛型迭代子定义, 需要额外实现IEnumerator<E>接口 |
14 | /// </summary> |
15 | public struct Enumertor<E> : IEnumerator, IEnumerator<E> { |
16 | |
17 | /// <summary> |
18 | /// 表示迭代索引, 即迭代器访问到向量的下标 |
19 | /// </summary> |
20 | private int index; |
21 | |
22 | /// <summary> |
23 | /// 一个到迭代器所属的向量类对象的引用 |
24 | /// </summary> |
25 | private List<E> list; |
26 | |
27 | /// <summary> |
28 | /// 参数构造器 |
29 | /// </summary> |
30 | /// <param name="container">表示迭代器所属的集合类</param> |
31 | public Enumertor(List<E> container) { |
32 | this.list = container; |
33 | this.index = -1; |
34 | } |
35 | |
36 | /// <summary> |
37 | /// 析构器. 后面会讲, 这里空实现 |
38 | /// </summary> |
39 | public void Dispose() { |
40 | } |
41 | |
42 | /// <summary> |
43 | /// 实现IEnumerator接口的Current属性 |
44 | /// 由于IEnumerator<T>接口同样具有一个Current属性, |
45 | /// 并且同IEnumerator接口的Current属性不同, 所以这里将 |
46 | /// IEnumerator接口的Current属性显示实现 |
47 | /// </summary> |
48 | object IEnumerator.Current { |
49 | get { |
50 | return list[index]; |
51 | } |
52 | } |
53 | |
54 | /// <summary> |
55 | /// 实现IEnumerator接口的Current属性 |
56 | /// </summary> |
57 | public E Current { |
58 | get { |
59 | return list[index]; |
60 | } |
61 | } |
62 | |
63 | /// <summary> |
64 | /// 将迭代器指示到“下一个数据位置” |
65 | /// </summary> |
66 | public bool MoveNext() { |
67 | if (this.index < list.Count) { |
68 | ++this.index; |
69 | } |
70 | return this.index < list.Count; |
71 | } |
72 | |
73 | /// <summary> |
74 | /// 将迭代器指示恢复到集合起始位置。 |
75 | /// </summary> |
76 | public void Reset() { |
77 | this.index = -1; |
78 | } |
79 | } |
80 | |
81 | /// <summary> |
82 | /// 保存数据的数组为泛型类型E类型 |
83 | /// </summary> |
84 | private E[] array; |
85 | |
86 | /// <summary> |
87 | /// 当前集合的长度 |
88 | /// </summary> |
89 | private int count; |
90 | |
91 | /// <summary> |
92 | /// 默认构造器 |
93 | /// </summary> |
94 | public List() |
95 | : this(1) { |
96 | } |
97 | |
98 | /// <summary> |
99 | /// 参数构造器 |
100 | /// </summary> |
101 | public List(int capacity) { |
102 | if (capacity < 0) { |
103 | throw new ArgumentOutOfRangeException("caption"); |
104 | } |
105 | if (capacity == 0) { |
106 | capacity = 1; |
107 | } |
108 | this.array = new E[capacity]; |
109 | this.count = 0; |
110 | } |
111 | |
112 | /// <summary> |
113 | /// 获取向量的长度 |
114 | /// </summary> |
115 | public int Count { |
116 | get { |
117 | return this.count; |
118 | } |
119 | } |
120 | |
121 | /// <summary> |
122 | /// 返回向量实际使用的长度 |
123 | /// </summary> |
124 | public int Capacity { |
125 | get { |
126 | return this.array.Length; |
127 | } |
128 | } |
129 | |
130 | /// <summary> |
131 | /// 是否固定大小的属性(返回常量false) |
132 | /// </summary> |
133 | public bool IsFixedSize { |
134 | get { |
135 | return false; |
136 | } |
137 | } |
138 | |
139 | /// <summary> |
140 | /// 是否只读集合的属性(返回常量false) |
141 | /// </summary> |
142 | public bool IsReadOnly { |
143 | get { |
144 | return false; |
145 | } |
146 | } |
147 | |
148 | /// <summary> |
149 | /// 集合是否可同步属性, 返回false, 表示集合无法同步 |
150 | /// </summary> |
151 | public bool IsSynchronized { |
152 | get { |
153 | return false; |
154 | } |
155 | } |
156 | |
157 | /// <summary> |
158 | /// 同步对象属性, 返回null, 表示集合无需同步 |
159 | /// </summary> |
160 | public object SyncRoot { |
161 | get { |
162 | return null; |
163 | } |
164 | } |
165 | |
166 | /// <summary> |
167 | /// 当this.array长度不够时, 重新分配长度足够的新数组。 |
168 | /// </summary> |
169 | private E[] GetNewArray() { |
170 | |
171 | // 这里分配的是泛型E类型的数组 |
172 | return new E[(this.array.Length + 1) * 2]; |
173 | } |
174 | |
175 | /// <summary> |
176 | /// 实现IList<E>接口的Add方法, 该方法采用泛型参数value |
177 | /// </summary> |
178 | public void Add(E value) { |
179 | int newCount = this.count + 1; |
180 | if (this.array.Length < newCount) { |
181 | |
182 | // 获取新的E类型数组 |
183 | E[] newArray = GetNewArray(); |
184 | Array.Copy(this.array, newArray, this.count); |
185 | this.array = newArray; |
186 | } |
187 | |
188 | this.array[this.count] = value; |
189 | this.count = newCount; |
190 | } |
191 | |
192 | |
193 | /// <summary> |
194 | /// 向向量的末尾添加对象 |
195 | /// 调用前一个Add方法即可 |
196 | /// </summary> |
197 | int IList.Add(object value) { |
198 | |
199 | // 通过下面这句代码, 看看怎么调用当前类实现另一个接口的同名方法 |
200 | ((IList<E>)this).Add((E)value); |
201 | return this.count - 1; |
202 | } |
203 | |
204 | /// <summary> |
205 | /// 实现IList<E>接口的索引器属性, 属性的类型为泛型类型E |
206 | /// </summary> |
207 | public E this[int index] { |
208 | get { |
209 | if (index < 0 || index >= this.count) { |
210 | throw new ArgumentOutOfRangeException("index"); |
211 | } |
212 | return this.array[index]; |
213 | } |
214 | set { |
215 | if (index < 0 || index >= this.count) { |
216 | throw new ArgumentOutOfRangeException("index"); |
217 | } |
218 | this.array[index] = value; |
219 | } |
220 | } |
221 | |
222 | /// <summary> |
223 | /// 显示实现IList接口的索引器属性 |
224 | /// 调用前一个索引器属性即可 |
225 | /// </summary> |
226 | object IList.this[int index] { |
227 | get { |
228 | return ((IList<E>)this)[index]; |
229 | } |
230 | set { |
231 | ((IList<E>)this)[index] = (E)value; |
232 | } |
233 | } |
234 | |
235 | /// <summary> |
236 | /// 删除向量中的元素 |
237 | /// </summary> |
238 | public void RemoveRange(int index, int count) { |
239 | if (index < 0) { |
240 | throw new ArgumentOutOfRangeException("index"); |
241 | } |
242 | int removeIndex = index + count; |
243 | if (count < 0 || removeIndex > this.count) { |
244 | throw new ArgumentOutOfRangeException("count"); |
245 | } |
246 | Array.Copy(this.array, index + 1, this.array, index + count - 1, this.count - removeIndex); |
247 | this.count -= count; |
248 | } |
249 | |
250 | /// <summary> |
251 | /// 实现IList<E>接口的Remove方法 |
252 | /// 返回是否删除元素 |
253 | /// </summary> |
254 | public bool Remove(E value) { |
255 | int index = this.IndexOf(value); |
256 | if (index >= 0) { |
257 | this.RemoveRange(index, 1); |
258 | return true; |
259 | } |
260 | return false; |
261 | } |
262 | |
263 | /// <summary> |
264 | /// 实现IList接口的Remove方法, 此处为显示实现 |
265 | /// 调用前一个Remove方法即可 |
266 | /// </summary> |
267 | void IList.Remove(object value) { |
268 | ((IList<E>)this).Remove((E)value); |
269 | } |
270 | |
271 | /// <summary> |
272 | /// 从集合的特定位置删除对象的引用 |
273 | /// </summary> |
274 | public void RemoveAt(int index) { |
275 | RemoveRange(index, 1); |
276 | } |
277 | |
278 | /// <summary> |
279 | /// 实现IList<E>接口的IndexOf方法 |
280 | /// </summary> |
281 | public int IndexOf(E value) { |
282 | int index = 0; |
283 | if (value == null) { |
284 | while (index < this.count) { |
285 | if (this.array[index] == null) { |
286 | return index; |
287 | } |
288 | ++index; |
289 | } |
290 | } else { |
291 | while (index < this.count) { |
292 | if (value.Equals(this.array[index])) { |
293 | return index; |
294 | } |
295 | ++index; |
296 | } |
297 | } |
298 | return -1; |
299 | } |
300 | |
301 | /// <summary> |
302 | /// 实现IList接口的IndexOf方法, 此时为显示实现 |
303 | /// 调用前面的IndexOf方法即可 |
304 | /// </summary> |
305 | int IList.IndexOf(object value) { |
306 | return ((IList<E>)this).IndexOf((E)value); |
307 | } |
308 | |
309 | /// <summary> |
310 | /// 查找对应的数组项. |
311 | /// </summary> |
312 | public int IndexOf(object o, System.Collections.IComparer comparer) { |
313 | int index = 0; |
314 | while (index < this.count) { |
315 | if (comparer.Compare(this.array[index], o) == 0) { |
316 | return index; |
317 | } |
318 | ++index; |
319 | } |
320 | return -1; |
321 | } |
322 | |
323 | /// <summary> |
324 | /// 弹出向量的最后一个元素(即获取最后一个元素的引用后删除最后一个元素) |
325 | /// </summary> |
326 | public object PopBack() { |
327 | object o = this.array[this.count - 1]; |
328 | RemoveAt(this.count - 1); |
329 | return o; |
330 | } |
331 | |
332 | /// <summary> |
333 | /// 弹出向量的第一个对象 |
334 | /// </summary> |
335 | public object PopFront() { |
336 | object o = this.array[0]; |
337 | RemoveAt(0); |
338 | return o; |
339 | } |
340 | |
341 | /// <summary> |
342 | /// 实现IList<E>接口的Insert方法 |
343 | /// </summary> |
344 | public void Insert(int index, E value) { |
345 | if (index >= this.count) { |
346 | throw new ArgumentOutOfRangeException("index"); |
347 | } |
348 | int newCount = this.count + 1; |
349 | if (this.array.Length < newCount) { |
350 | E[] newArray = GetNewArray(); |
351 | Array.Copy(this.array, newArray, index); |
352 | newArray[index] = value; |
353 | Array.Copy(this.array, index, newArray, index + 1, this.count - index); |
354 | this.array = newArray; |
355 | } else { |
356 | Array.Copy(this.array, index, this.array, index + 1, this.count - index); |
357 | this.array[index] = value; |
358 | } |
359 | this.count = newCount; |
360 | } |
361 | |
362 | /// <summary> |
363 | /// 实现IList接口的Insert方法 |
364 | /// </summary> |
365 | void IList.Insert(int index, object value) { |
366 | ((IList<E>)this).Insert(index, (E)value); |
367 | } |
368 | |
369 | /// <summary> |
370 | /// 实现IList<E>接口的Contains方法 |
371 | /// </summary> |
372 | public bool Contains(E value) { |
373 | return this.IndexOf(value) >= 0; |
374 | } |
375 | |
376 | |
377 | /// <summary> |
378 | /// 实现IList接口的Contains方法 |
379 | /// </summary> |
380 | bool IList.Contains(object value) { |
381 | return ((IList<E>)this).IndexOf((E)value) >= 0; |
382 | } |
383 | |
384 | /// <summary> |
385 | /// 将内部数组的长度压缩为向量的实际长度 |
386 | /// </summary> |
387 | public void TrimToSize() { |
388 | if (this.array.Length > this.count) { |
389 | |
390 | // 这里需要实例化一个泛型数组 |
391 | E[] newArray = null; |
392 | if (this.count > 0) { |
393 | newArray = new E[this.count]; |
394 | Array.Copy(this.array, newArray, this.count); |
395 | } else { |
396 | newArray = new E[1]; |
397 | } |
398 | this.array = newArray; |
399 | } |
400 | } |
401 | |
402 | /// <summary> |
403 | /// 清空向量 |
404 | /// </summary> |
405 | public void Clear() { |
406 | this.count = 0; |
407 | } |
408 | |
409 | /// <summary> |
410 | /// 实现IEnumerable<E>接口的GetEnumerator方法 |
411 | /// </summary> |
412 | public IEnumerator<E> GetEnumerator() { |
413 | Enumertor<E> ator = new Enumertor<E>(this); |
414 | return ator; |
415 | } |
416 | |
417 | /// <summary> |
418 | /// 实现IEnumerable接口的GetEnumerator方法, 显示实现 |
419 | /// 调用前面的GetEnumerator方法即可 |
420 | /// </summary> |
421 | IEnumerator IEnumerable.GetEnumerator() { |
422 | return ((IEnumerable<E>)this).GetEnumerator(); |
423 | } |
424 | |
425 | /// <summary> |
426 | /// 实现ICollection<E>接口的CopyTo方法 |
427 | /// </summary> |
428 | public void CopyTo(E[] array, int index) { |
429 | Array.Copy(this.array, 0, array, index, this.count); |
430 | } |
431 | |
432 | |
433 | /// <summary> |
434 | /// 实现ICollection接口的CopyTo方法, 显示实现 |
435 | /// </summary> |
436 | void ICollection.CopyTo(Array array, int index) { |
437 | Array.Copy(this.array, 0, array, index, this.count); |
438 | } |
439 | } |
440 | } |
上述代码展示了一个支持泛型的向量集合,从中我们可以学习到泛型的一些知识。注意以下几点:
- 通过代码可以看到,将一个不支持泛型的集合类改为支持泛型的集合类非常容易。算法,类结构都无需变化,只需要将原本为object类型的字段改为泛型类型E类型的字段即可。当然访问该字段的所有方法和属性都要进行相应改动。
- 这个类总共实现了如下接口:IEnumerable, IEnumerable<E>, ICollection, ICollection<E>, IList, IList<E>,迭代子类则实现了IEnumerator和IEnumerator<E>接口。由于这些接口的主要区别在于泛型和非泛型,方法和属性名称都相同,所以采用显式实现的方式来实现非泛型的一系列接口。一方面将这些接口方法隐藏起来,另一方面防止因方法重名造成重载。
现在我们比较一下List<E>和ArrayList使用起来有什么不同。
1 | class Program { |
2 | static void Main(string[] args) { |
3 | |
4 | // 声明向量集合对象 |
5 | ArrayList lst = new ArrayList(); |
6 | |
7 | // 当调用Add方法时, 值类型(int)会自动的"装箱"为引用类型(object) |
8 | lst.Add(100); |
9 | |
10 | // 当调用索引器时, 返回的是一个object类型的对象引用 |
11 | object o = lst[0]; |
12 | |
13 | // 要恢复存储前的类型, 这里还需要一次"拆箱"操作 |
14 | int value = (int)o; |
15 | |
16 | Console.WriteLine(value); |
17 | |
18 | // 这里, 先进行了一次"拆箱"操作, 将lst[0]转化为int类型 |
19 | // 接着进行了一次装箱操作 |
20 | lst[0] = (int)lst[0] + 200; |
21 | Console.WriteLine(lst[0]); |
22 | |
23 | // 对于ArrayList, 可以存放任意类型的对象引用 |
24 | lst.Add("Hello"); |
25 | |
26 | foreach (object v in lst) { |
27 | // 这里只能访问object类型的ToString方法 |
28 | // 并无法知道object类型的v变量到底是什么类型 |
29 | Console.WriteLine(v); |
30 | } |
31 | |
32 | // 这里使用了List<E>类, 和ArrayList类比较下, 看区别在哪里 |
33 | List<int> slst = new List<int>(); |
34 | slst.Add(100); |
35 | slst.Add(200); |
36 | foreach (int n in slst) { |
37 | Console.WriteLine(n); |
38 | } |
39 | } |
40 | } |
本章代码下载
看,泛型确定并强制了集合存储的对象类型,并且避免了装箱拆箱操作。既高效又安全。