微软的.Net Framework源码到:http://www.cnblogs.com/ghost_boy/archive/2008/07/16/1244229.html 下载.
Mono的.Net Framework源码到:http://go-mono.com/sources-stable/ 下载第一个,现在是mono-1.9.1.tar.bz2.
两个源码质量都很高,反正就是比小菜高.
从System.Collections开始吧,集合对于我们来说太熟悉不过了,我们天天都在用它.
Array,ArrayList,Stack,Queue等等
而且现在小菜热血沸腾,要不等下灭了就不好了.
一)当foreach还未来到这个世界,遍历集合的世界是怎么样的? (挺黑暗的,不信啊,那你接着往下瞧)
看看小菜做了些什么,噢,小菜使用Array来存储Person,并且遍历该Array
一些朋友开始不满了,小菜你就让我们看这个啊,这也太简单了吧.( :) 那让我们接着往下看,会有更多惊喜)
我们先来看看ArrayList与Array遍历的不同之处吧!
1)Array使用persons.Length来确定长度,ArrayList使用persons.Count来确定长度.
2)Array使用persons.GetValue(i)来取出当前元素,ArrayList使用persons[i]来取出当前元素.
一些朋友开始抱怨代码还是太简单了,那就来个有难度点的Stack(栈)
不知道大家还记不记得这个数组结构中的老大级人物(后进先出)
不知道有没有朋友产生疑问?
问题1) for(int i=0; i<persons.Count;)中怎么没有i++啊?
问题2) 将for(int i=0; i<persons.Count;){/*省略*/} 省略中的代码替换成如下代码又会出现什么问题呢?
Console.WriteLine("姓名:" + ((Person)persons.Pop()).Name);
Console.WriteLine("姓名:" + ((Person)persons.Pop()).Name);
输出结果: 姓名:小北 年龄:13 姓名:小西 年龄:11
?奇了怪了怎么小南与小东没有被输出呢
至于为什么,我们来走一遍运行过程吧.
1. i=0; persons.Count=4; p= 小北
2. i=0; persons.Count=3; p= 小南
3. i=0; persons.Count=2; p= 小西
4. i=0; persons.Count=1; p= 小东
Stack中的Pop会返回栈顶元素,并弹出,所以会将count减一,并且数组的索引减一.
所以使用上面的代码,Pop()两次,相当于count减二,数组的索引减二,所以小南和小东不会被输出.
我们通过Stack中的关键源码来加深下理解.
看来我们遇到麻烦了,如果我们不理解Stack这个数据结构,那么对它的遍历操作我们将无法实现.
我们的设计一直提倡针对接口编程,而现在我们却在针对实现编程,看来挺失败的.
具体的数据结构将在很大程度上影响我们的实现.
如果你明白了这一句话那么小菜就没有必要在举例: 队列Queue,哈希表HashTable等等了,小菜也轻松了. :)
二)foreach将我们脱离了苦海.(那么是谁让foreach脱离了苦海呢?)
Iterator模式让foreach脱离苦海.
接下来就让我们进入迭代模式(Iterator模式),这个都快被大家遗忘的模式.
我们先来看看foreach的实现吧!
确实简单,我们无需知道遍历集合的任何比较细节.
我们无需知道遍历集合的任何实现.
那foreach是怎么做到的呢?
我们来看看foreach被翻译成什么(代码已简化)
这时我们将用到一个最重要的设计原则: 对象的职责划分
对象集合的职责是存储对象,遍历对象集合的行为是不属于集合的职责.
所以我们可以将遍历对象集合的行为独立出来成为接口:IEnumerator
对象集合遍历的操作不外乎:1.判断是否还有下一个元素 MoveNext() 2.取出当前元素 Current
那么我们就来定义一个接口吧:IEnumerator(称为枚举器,名称不同罢了,功能是相同的,和java中的接口Iterator迭代器一样)
从Array开始吧.
这里说下:return new SZArrayEnumerator(this)表示遍历的数组为一维的时候使用它.
return new ArrayEnumerator(this,lowerBound,Length);表示遍历的数组为多维的时候使用它.
lowerBound: 为第一维的下限
Lenght: 为所有维数中元素的总数
遍历多维的实现也很有趣,主要使用两个函数一个属性:
1.array.GetUpperBound(i) i维的上限 2.array.GetLowerBound(i) i维的下限 3.array.Rank 有几维
如果你对具体实现感兴趣,那就打开你手中的.Net Framework中的Array.cs源码吧.
按这样说,那ArrayList也应该有类似的实现.那就让我们来看看ArrayList的源码,来验证这个想法.
看来事实验证了我们的想法.
还有什么可以改进的呢?
我们看到Array与ArrayList等各对象集合都实现了 IEnumerator GetEnumerator()
那么我们是不是应该为它们定义一个接口呢?当然. IEnumerable
现在就让我们来看看UML图,让我们更清晰点.
这便是迭代模式.
它在.Net Framework中真可谓无处不在.
(应该会联想到迭代模式吧,foreach便是它给予的魔力,如果还联想不到,那小菜真是太失败了)
看来string也应该有提供类似的代码.否则它怎么能foreach呢?
那就让我们来看看String的代码吧.(string是String的别名而已)
类似之前的Array中的SZArrayEnumerator 还有 ArrayList中的ArrayListEnumeratorSimple
.Net在2.0的时候引入了泛型,所以也对IEnumerable和IEnumerator做了改进.
引入了IEnumerable<T>与IEnumerator<T>
那看来小菜也要与时俱进.来实现个.
其实上面的public class Persons : IEnumerable<Person>,System.Collections.IEnumerable
完全可以用public class Persons : IEnumerable<Person>来替换,不过为了增强代码的可读性,所以小菜加上.
这种做法很类似微软的ArrayList : IList , ICollection, IEnumerable 也可被替换为 ArrayList : IList
需要注意的地方
1.实现IEnumerable<T> 要记得实现IEnumerable
2.同理实现IEnumerator<T> 要记得实现IEnumerator和IDisposable
那接下来就来使用Persons吧
其实上面的两种遍历方法是等效的,可相互转换.
四)有没有更简单的实现方式啊.有使用yield (在.Net2.0中引入的关键字)
通过yield告诉编译器在什么时候返回什么值,再由编译器自动完成实现IEnumerator<Person>接口的登记工作。
我们还可以使用yield break语句支持从迭代块中直接结束.
:) 就到这里.