今天介绍一下迭代器的使用,在.net平台中迭代器是通过IEnumerator和IEnumerable接口及其它们的泛型等价物来封装的,然而提到迭代器我们会立刻想到yield return语句,yield return 是在C#1以后出现的,所以其实在C#1编写迭代器的时候是非常痛苦的事情。我们就从迭代器的发展着手,来讲解一下迭代器的使用。
1.下面的代码是手动实现的一个迭代器(C#1.0)
class MyListPerson : IEnumerable { Person[] persons; public MyListPerson(Person[] ps) { this.persons = ps; } public IEnumerator GetEnumerator() { return new MyListPersonIterator(this); } class MyListPersonIterator : IEnumerator { private int position = -1; private MyListPerson person; public MyListPersonIterator(MyListPerson p) { this.person = p; } public object Current { get { if (position == -1 || position >= person.persons.Count()) { throw new Exception(); } return person.persons[position]; } } public bool MoveNext() { if (person.persons.Length != position) { position++; } return position < person.persons.Length; } public void Reset() { position = -1; } } }
现在我们来测试一下自己写的一个迭代列表,看看效果:
Person[] persons = {
new Person { name = "wangjian", age = 23 },
new Person { name = "zhangtao", age = 27 },
new Person { name = "wangdongming", age = 23 },
new Person { name = "wuguang", age = 23 }
};
MyListPerson p = new MyListPerson(persons); foreach (Person temp in p) { Console.WriteLine("Name: {0} ; Age: {1}", temp.name, temp.age); }
这是完全手动实现的迭代器,是多么的麻烦,我必须实现IEnumberable和IEnumberator这两个接口,并且将接口代码全部实现,其实实现IEnumberable是简单的,但是模拟迭代器的部分稍显复杂,如果你对迭代器的认识比较模糊那么暂且就将迭代器想象成为DataReader吧,把它当做就是一个游标的功能,我们再来看看通过yield return 实现的迭代器(C#2.0):
public IEnumerator GetEnumerator() { //return new MyListPersonIterator(this); for (int i = 0; i < persons.Length; i++) { yield return persons[i];//进行foreach 迭代 } }
上面短短几行代码实现了我们整个MyListPersonIterator的整个类的功能,C#的yield return神奇的语法糖大大的简化了我们的代码操作。那编译器到底是怎么帮助yield return 实现迭代器功能的呢? 实际上在你使用yield return语句的时候,编译器会为你提供一个状态机,代替MyListPersonIterator的功能,我们每次只想获取一个元素,所以在返回给我们一个值得时候,状态机会跟踪当前工作状态。在迭代器代码中,编译器创建了一个状态机,来记录我们在迭代器代码块中所处的位置和局部变量在该处的值。编译器会分析迭代块,创建一个类似于我们之前用普通方法实现的类MyListPersonIterator,并用实例变量来保存所必要的状态。yield return 的内部机制就介绍到这里,下面让我们来看下一迭代器的工作原理以及我们在使用时候的注意事项:
2.下面的代码显示了迭代器和调用者的执行顺序
static IEnumerable<Person> CreateEnumberable(Person[] ps) { Person[] persons = ps; for (int i = 0; i < 4; i++) { Console.WriteLine(i + ". Before yield return"); yield return persons[i]; Console.WriteLine(i + ". After yield return"); Console.WriteLine(); } Console.WriteLine(" Yield return completed!"); } .... static void Main(string[] args) { Person[] persons = {
new Person { name = "wangjian", age = 23 },
new Person { name = "zhangtao", age = 27 },
new Person { name = "wangdongming", age = 23 }, new Person { name = "wuguang", age = 23 } };
IEnumerable<Person> pers = CreateEnumberable(persons); IEnumerator<Person> personIterator = pers.GetEnumerator(); while (true) { Console.WriteLine(" Move nexting..."); bool result = personIterator.MoveNext(); if (!result) { break; } Console.WriteLine(" Get current... Current Name: " + personIterator.Current.name); }
运行上面代码逐句调试的时候你会看到,在程序运行到yield return 语句的时候CreateEnumberable程序会暂停,程序会跳回到调用者程序中(Main)运行MoveNext()下面的代码,当下次程序再次调用MoveNext()的时候才会在CreateEnumberable的yield return 暂停处继续执行,同时我们在调用Current的时候并没有执行CreateEnumberable迭代块中的任何代码,说明迭代块处理处理的数据在调用MoveNext()的时候已经处理完成了。
3.yield break 跳出迭代块
public IEnumerator GetEnumerator() { //return new MyListPersonIterator(this); for (int i = 0; i < persons.Length; i++) { if (persons[i].name == "zhangtao") yield break; yield return persons[i];//进行foreach 迭代 } Console.WriteLine("yield return complete"); }
程序会在现实第一条记录的时候直接停止迭代,程序结束。
3. 最后关于Finally块的执行
finally代码块往往和try块一起使用,表示在程序退出的时候释放还没有释放的资源。下面我们来解释一下在迭代器中使用finally块的注意事项。我们对上面的代码稍加改动得到如下代码:
static IEnumerable<Person> CreateEnumberable(Person[] ps) { Person[] persons = ps; try { for (int i = 0; i < 4; i++) { Debug.WriteLine(i + ". Before yield return"); yield return persons[i]; Debug.WriteLine(i + ". After yield return"); } } finally { Debug.WriteLine(" Yield return completed!"); } } static void Main(string[] args) { //ILog logger = LogManager.GetLogger("TIME"); // logger.Info("MESSAGE"); Person[] persons = { new Person { name = "wangjian", age = 23 }, new Person { name = "zhangtao", age = 27 }, new Person { name = "wangdongming", age = 23 }, new Person { name = "wuguang", age = 23 } };
//代码1 自动调用的时候,在程序完成时候会自动释放资源也就是自动调用Dispose()方法 //foreach (Person p in CreateEnumberable(persons)) //{ // Debug.WriteLine("Current Value: " + p.name); // if (p.name == "zhangtao") // { // return; // } //} //代码2 手动调用Dispose IEnumerable<Person> pers = CreateEnumberable(persons); IEnumerator<Person> personIterator = pers.GetEnumerator(); while (true) { bool result = personIterator.MoveNext(); Debug.WriteLine("Current value:" + personIterator.Current.name); if (personIterator.Current.name == "zhangtao") { personIterator.Dispose();//如果执行了 那么就会调用finally return; } if (!result) break; }
foreach循环中的return 执行后,迭代器的finally块代码也是被执行了的。我们知道finally通常是用来释放资源的,例如你使用uisng代码块打开一个数据库连接(using代码块也就是try/finally代码块),foreach循环会在自己的finally代码块中调用IEnumberator所提供的Dispoes()方法(见代码1),在迭代器完成任务之前,如果在Main方法中手动调用迭代器上的Dispose,那么状态机也会执行finally代码块(见代码2)。直白的说就是只要调用者使用了foreach循环,迭代器块中的finally将会在你退出迭代器的时候(包括异常)调用finally块。
关于C#的迭代器就介绍到这里,希望对大家有所帮助。我也是C#的入门选手,所以如果大家对我介绍的内容有什么其他的想法或者建议可以给我留言。希望与大家多多交流,共同进步。