什么是IEnumerable?
IEnumerable及IEnumerable的泛型版本IEnumerable<T>是一个接口,它只含有一个方法GetEnumerator。Enumerable这个静态类型含有很多扩展方法,其扩展的目标是IEnumerable<T>。
实现了这个接口的类可以使用Foreach关键字进行迭代(迭代的意思是对于一个集合,可以逐一取出元素并遍历之)。实现这个接口必须实现方法GetEnumerator。
实现一个继承IEnumerable的类型等同于实现方法GetEnumerator。想知道如何实现方法GetEnumerator,不妨思考下实现了GetEnumerator之后的类型在Foreach之下的行为:
- 可以获得第一个或当前成员
- 可以移动到下一个成员
- 可以在集合没有下一个成员时退出循环。
假设我们有一个很简单的Person类(例子来自MSDN):
public class Person { public Person(string fName, string lName) { FirstName = fName; LastName = lName; } public string FirstName; public string LastName; }
然后我们想构造一个没有实现IEnumerable的类型,其储存多个Person,然后再对这个类型实现IEnumerable。这个类型实际上的作用就相当于Person[]或List<Person>,但我们不能使用它们,因为它们已经实现了IEnumerable,故我们构造一个People类,模拟很多人(People是Person的复数形式)。这个类型允许我们传入一组Person的数组。所以它应当有一个Person[]类型的成员,和一个构造函数,其可以接受一个Person[],然后将Person[]类型的成员填充进去作为初始化。
//People类就是Person类的集合 //但我们不能用List<Person>或者Person[],因为他们都实现了IEnumerable //我们要自己实现一个IEnumerable //所以请将People类想象成List<Person>或者类似物 public class People : IEnumerable { private readonly Person[] _people; public People(Person[] pArray) { //构造一个Person的集合 _people = new Person[pArray.Length]; for (var i = 0; i < pArray.Length; i++) { _people[i] = pArray[i]; } } //实现IEnumerable需要实现GetEnumerator方法 public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }
我们的主函数应当是:
public static void Main(string[] args) { //新的Person数组 Person[] peopleArray = { new Person("John", "Smith"), new Person("Jim", "Johnson"), new Person("Sue", "Rabon"), }; //People类实现了IEnumerable var peopleList = new People(peopleArray); //枚举时先访问MoveNext方法 //如果返回真,则获得当前对象,返回假,就退出此次枚举 foreach (Person p in peopleList) Console.WriteLine(p.FirstName + " " + p.LastName); } 复制代码
但现在我们的程序不能运行,因为我们还没实现GetEnumerator方法。
实现方法GetEnumerator
GetEnumerator方法需要一个IEnumerator类型的返回值,这个类型是一个接口,所以我们不能这样写:
return new IEnumerator();
因为我们不能实例化一个接口。我们必须再写一个类PeopleEnumerator,它继承这个接口,实现这个接口所有的成员:Current属性,两个方法MoveNext和Reset。于是我们的代码又变成了这样:
//实现IEnumerable需要实现GetEnumerator方法 public IEnumerator GetEnumerator() { return new PeopleEnumerator(); }
在类型中:
public class PeopleEnumerator : IEnumerator { public bool MoveNext() { throw new NotImplementedException(); } public void Reset() { throw new NotImplementedException(); } public object Current { get; } }
现在问题转移为实现两个方法,它们的功能看上去一目了然:一个负责将集合中Current向后移动一位,一个则将Current初始化为0。我们可以查看IEnumerator元数据,其解释十分清楚:
- Enumerator代表一个类似箭头的东西,它指向这个集合当前迭代指向的成员
- IEnumerator接口类型对非泛型集合实现迭代
- Current表示集合当前的元素,我们需要用它仅有的get方法取得当前元素
- MoveNext方法根据Enumerator是否可以继续向后移动返回真或假
- Reset方法将Enumerator移到集合的开头
通过上面的文字,我们可以理解GetEnumerator方法,就是获得当前Enumerator指向的成员。我们引入一个整型变量position来记录当前的位置,并且先试着写下:
public class PeopleEnumerator : IEnumerator { public Person[] _peoples; public object Current { get; } //当前位置 public int position; //构造函数接受外部一个集合并初始化自己内部的属性_peoples public PeopleEnumerator(Person[] peoples) { _peoples = peoples; } //如果没到集合的尾部就移动position,返回一个bool public bool MoveNext() { if (position < _peoples.Length) { position++; return true; } return false; } public void Reset() { position = 0; } }
这看上去好像没问题,但一执行之后却发现:
- 当执行到MoveNext方法时,position会先增加1,这导致第一个元素(在位置0)会被遗漏,故position的初始值应当为-1而不是0
- 当前位置变量position显然应该是私有的
- 需要编写Current属性的get方法取出当前位置(position)上的集合成员
通过不断的调试,最后完整的实现应当是:
public class PeopleEnumerator : IEnumerator { public Person[] People; //每次运行到MoveNext或Reset时,利用get方法自动更新当前位置指向的对象 object IEnumerator.Current { get { try { //当前位置的对象 return People[_position]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } //当前位置 private int _position = -1; public PeopleEnumerator(Person[] people) { People = people; } //当程序运行到foreach循环中的in时,就调用这个方法获得下一个person对象 public bool MoveNext() { _position++; //返回一个布尔值,如果为真,则说明枚举没有结束。 //如果为假,说明已经到集合的结尾,就结束此次枚举 return (_position < People.Length); } public void Reset() => _position = -1; }
为什么当程序运行到in时,会呼叫方法MoveNext呢?我们并没有直接调用这个方法啊?当你试图查询IL时,就会得到答案。实际上下面两段代码的作用是相同的:
foreach (T item in collection) { ... }
IEnumerator<T> enumerator = collection.GetEnumerator(); while (enumerator.MoveNext()) { T item = enumerator.Current; ... }
转载:https://www.cnblogs.com/haoyifei/p/5768379.html