迭代器
引言:
在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerable或IEnumerable<T>接口,(之所以必须要实现IEnumerable这个接口,是因为foreach是迭代语句,要使用foreach必须要有一个迭代器才行的,然而IEnumerable接口中就有IEnumerator GetEnumerator()方法是返回迭代器的,所以实现了IEnumerable接口,就必须实现GetEnumerator()这个方法来返回迭代器,有了迭代器就自然就可以使用foreach语句了), 而在C# 1.0中要获得迭代器就必须实现IEnumerable接口中的GetEnumerator()方法,然而要实现一个迭代器就必须实现IEnumerator接口中的bool MoveNext()和void Reset()方法,以及Current属性,然而 C# 2.0中提供 yield关键字来简化迭代器的实现,这样在C# 2.0中如果我们要自定义一个迭代器就容易多了。下面就具体介绍了C# 2.0 中如何提供对迭代器的支持.
一、迭代器的介绍
迭代器大家可以想象成数据库的游标,即一个集合中的某个位置,C# 1.0中使用foreach语句实现了访问迭代器的内置支持,使用foreach使我们遍历集合更加容易(比使用for语句更加方便,并且也更加容易理解),foreach被编译后会调用GetEnumerator来返回一个迭代器,也就是一个集合中的初始位置(foreach其实也相当于是一个语法糖,把复杂的生成代码工作交给编译器去执行)。
二、C#1.0如何实现迭代器
在C# 1.0 中实现一个迭代器必须实现IEnumerator接口,下面代码演示了传统方式来实现一个自定义的迭代器:
class Program { static void Main(string[] args) { List<Person> personList = new List<Person> { new Person {Id = 1, Name = "wjire1"}, new Person {Id = 2, Name = "wjire2"}, new Person {Id = 3, Name = "wjire3"}, new Person {Id = 4, Name = "wjire4"}, new Person {Id = 5, Name = "wjire5"}, new Person {Id = 6, Name = "wjire6"}, }; Persons persons = new Persons(personList); foreach (var person in persons) { Console.WriteLine(person); } Console.ReadKey(); } } /// <summary> /// 人的集合 /// </summary> public class Persons : IEnumerable { private readonly List<Person> _persons; public Persons(List<Person> persons) { _persons = persons; } public IEnumerator GetEnumerator() { return new MyIterator(this); } public Person this[int index] => _persons[index]; public int Count => _persons.Count; } /// <summary> /// 要实现自己的迭代器, 必须实现 IEnumerator 接口,这样就必须实现 IEnumerator 接口中的MoveNext、Reset方法和Current属性 /// </summary> public class MyIterator : IEnumerator { private readonly Persons _persons; private int _index; private Person _current; public MyIterator(Persons persons) { _persons = persons; _index = 0; } public bool MoveNext() { if (_index + 1 <= _persons.Count) { this._current = _persons[_index]; _index++; return true; } return false; } public void Reset() { _index = 0; } public object Current => this._current; }
或者( 推荐 泛型接口 )
public class Person { public int Id { get; set; } public Person(int id) { this.Id = id; } public override string ToString() { return this.Id.ToString(); } } public class Persons : IEnumerable<Person> { public Person[] PersonArray { get; set; } public Person this[int index] => PersonArray[index]; public int Count => PersonArray.Length; public IEnumerator<Person> GetEnumerator() { return new PersonEnumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } public class PersonEnumerator : IEnumerator<Person> { private Persons persons { get; } private int index { get; set; } public PersonEnumerator(Persons ps) { this.persons = ps; this.index = 0; } public bool MoveNext() { if (index < persons.Count) { this.Current = persons[index]; index++; return true; } return false; } public void Reset() { index = 0; } public Person Current { get; set; } object IEnumerator.Current => this.Current; public void Dispose() { } }
static void Main(string[] args)
{
Person[] persons ={
new Person(1),
new Person(2),
new Person(3),
new Person(4),
new Person(5),
new Person(6),
};
Persons ps = new Persons { PersonArray = persons };
foreach (var person in ps)
{
Console.WriteLine(person);
}
Console.ReadKey();
}
三、使用C#2.0的新特性简化迭代器的实现
/// <summary> /// 人的集合 /// </summary> public class Persons : IEnumerable { private readonly List<Person> _persons; public Persons(List<Person> persons) { _persons = persons; } public IEnumerator GetEnumerator() { for (int i = 0; i < _persons.Count; i++) { //只需要这一句话就OK了 yield return _persons[i]; } } public Person this[int index] => _persons[index]; public int Count => _persons.Count; }
在上面代码中有一个yield return 语句,这个语句的作用就是告诉编译器GetEnumerator方法不是一个普通的方法,而是实现一个迭代器的方法,当编译器看到yield return语句时,编译器知道需要实现一个迭代器,所以编译器生成中间代码时为我们生成了一个IEnumerator接口的对象,大家可以通过Reflector工具进行查看,下面是通过Reflector工具得到一张截图:
从上面截图可以看出,yield return 语句其实是C#中提供的另一个语法糖,简化我们实现迭代器的源代码,把具体实现复杂迭代器的过程交给编译器帮我们去完成,看来C#编译器真是做得非常人性化,把复杂的工作留给自己做,让我们做一个简单的工作就好了。
迭代器工作流程
class Program { private static readonly string Padding = new string(' ', 30); static void Main(string[] args) { IEnumerable<int> iEnumerable = CreateIEnumerable();//在第一次 调用 iEnumerator.MoveNext() 方法前,CreateIEnumerable() 方法里面的代码不会被执行 IEnumerator<int> iEnumerator = iEnumerable.GetEnumerator(); Console.WriteLine("Starting to iterable"); while (true) { Console.WriteLine("Calling MoveNext()"); bool result = iEnumerator.MoveNext();//所有工作在 iEnumerator.MoveNext() 调用时就完成了,后面的 iEnumerator.Current 不会执行任何代码 Console.WriteLine(" Move result = {0}", result); if (!result) { break; } Console.WriteLine("Fetching Current"); Console.WriteLine($"Current result = {iEnumerator.Current}"); } Console.ReadKey(); } static IEnumerable<int> CreateIEnumerable() { Console.WriteLine($"{Padding} Start of CreateIEnumerable()"); for (int i = 0; i < 3; i++) { Console.WriteLine($"{Padding}About to yield {i}"); yield return i;//每次调用 iEnumerator.MoveNext() 方法时,代码执行到这里就停止,直到下一次调用 iEnumerator.MoveNext() 方法才会继续往下执行 Console.WriteLine($"{Padding}After yield"); } Console.WriteLine($"{Padding}Yielding final value"); yield return -1;//整个代码不会在这里结束,而是通过调用 iEnumerator.MoveNext() 方法返回 false 时才结束. Console.WriteLine($"{Padding}End of CreateIEnumerable"); } }
迭代器传参数
static void Test() { foreach (var i in CreateIEnumerable(DateTime.Now.AddSeconds(2))) { Console.WriteLine(i); if (i > 3) { Console.WriteLine("return"); return; } Thread.Sleep(300); } } static IEnumerable<int> CreateIEnumerable(DateTime stop) { try { for (int i = 0; i < 10000; i++) { if (DateTime.Now >= stop) { Console.WriteLine($"i = {i}"); yield break; } yield return i; } } finally { Console.WriteLine("yield end"); } }