首先搞清楚这两个接口,可以看出这两个接口是有一定的关联的,IEnumerable的接口成员是返回一个IEnumerator接口对象,
这个接口对象,有三个成员。
// 摘要:
// Exposes an enumerator, which supports a simple iteration over a non-generic
// collection.To browse the .NET Framework source code for this type, see the
// Reference Source.
[ComVisible(true)]
[Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerable
{
// 摘要:
// Returns an enumerator that iterates through a collection.
//
// 返回结果:
// An System.Collections.IEnumerator object that can be used to iterate through
// the collection.
[DispId(-4)]
IEnumerator GetEnumerator();
}
// 摘要:
// Supports a simple iteration over a non-generic collection.
[ComVisible(true)]
[Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerator
{
// 摘要:
// Gets the current element in the collection.
//
// 返回结果:
// The current element in the collection.
object Current { get; }
// 摘要:
// Advances the enumerator to the next element of the collection.
//
// 返回结果:
// true if the enumerator was successfully advanced to the next element; false
// if the enumerator has passed the end of the collection.
//
// 异常:
// System.InvalidOperationException:
// The collection was modified after the enumerator was created.
bool MoveNext();
//
// 摘要:
// Sets the enumerator to its initial position, which is before the first element
// in the collection.
//
// 异常:
// System.InvalidOperationException:
// The collection was modified after the enumerator was created.
void Reset();
}
这里首先还要区分两个概念,一个是调用这个迭代器,一个是实现这个迭代器,以前傻傻分不清楚。
先说调用这个迭代器,我们最常见的形式是使用foreach,编译器会将foreach编译来调用GetEnumerator和MoveNext和Current,
所以一般我们有两种方法来调用,本质是一样的:
再看看如何实现这个迭代器:
C#1的时候:
我们需要手工实现这个IEnumerable接口,为了实现接口,我们需要实现IEnumerator接口,生成一个内部类,然后再返回一个这个对象实例即可
我们发现,为了实现一个迭代器,我们需要做很多的工作,但是我们能看到具体的细节,可以看出,如果我们调用迭代器的时候,就跟调用方法一样,每调用一次,返回一个结果。
C#2.0的时候,实现迭代器,因为以前实现迭代器太繁琐,所以编译器打算自己跟我们把这些工作做好:
我们发现,简单了很多,不需要自己写内部类等,只需要特殊的关键字yield即可。
疑难问题:
1)书上说,迭代一个类,可使用方法GetEnumerator(),返回类型为IEnumerator,迭代一个类成员,则返回IEnumerable。
迭代一个类,自然要实现IEnumerable接口,这个接口的成员就是GetEnumerator,自然就应该返回IEnumerator。
迭代一个类成员,因为这个成员方法不会像类一样去实现什么接口,所以肯定要返回IEnumerable对象,所以内部也必须要能够帮助完成这一点,
要么有这么一个实现了这个接口的子类,要么使用yield,本质上一样的,因为只有IEnumerable才能被迭代,IEnumerator不能迭代。
2)不能在迭代块中写任何在方法调用时需要立即执行的代码-比如说参数验证。
这点可以这样来理解,因为在正式迭代之前,也就是调用MoveNext()方法之前,yield语句等才开始调用,所以就相当于即需加载一样。
3)关于finally块执行流程,其实很简单,如果是通过foreach来调用的,那么在yield break的时候,就会执行finally语句块,如果手动调用movenext()方法,则不会自动调用,
需要手工调用dispose方法,释放资源,这里IEnumerator跟其泛型是不一样的,IEnumerator<T>继承了IDisposable接口。迭代块里面的finally中的语句,就是最终Dispose方法
应该调用的地方,所以就解释了之前的疑惑:在迭代完成后会释放迭代器,这里是针对方法返回IEnumerable<T>的情况,如果是 类应该也是一样,也可以加finally吧,比如读取IO
资源的时候,在这里可以进行释放。
4)不能理解迭代器的优点在哪。
举一点例子,如果是查询文件,找到所有行,如果调用File.ReadAllLines,以及实现一个迭代器Array.FindAll, 后者在文件较大的时候就优势体现出来了,
后者是流线型处理的,一行一行读过来。
访问一个聚合对象的内容而无需暴露它的内部表示,所以可以提供聚合对象的多种遍历。
https://files.cnblogs.com/files/monkeyZhong/NewOverrideDemo.zip