-
C#教程之《转》C# IEnumerator 和IEnumerble 基本使用
试听地址 https://www.xin3721.com/eschool/CSharpxin3721/
1.开始
.net中迭代器是通过IEnumerable和IEnumerator接口来实现的
根据定义:
IEnumerable 只有一个返回IEnumerator 的GetEnumerator 无参方法。

IEnumerator 只有MoveNext和Reset 两个方法以及一个只读的Current属性。这个属性决定了foreach的实现以及foreach不能修改item的值。

首先继承IEnumerable 接口并实现:

然后继承IEnumerator 接口并实现:

接下来我们通过IEnumerator的基本方法和foreach来使用循环:

执行结果如下:

实现foreach不需要实现IEnumerable接口。 只要显式的实现IEnumerator GetEnumberator 无参方法即可。
在本例中,需要一个实现IEnumerator的类,因为我们在GetEnumerator中使用了它。
2.引入yield
如果通过实现一个IEnumerator的类去实现GetEnumerator方法有点麻烦, 这是我们可以通过yield的方式来实现。现在把GetEnumerator重写一下:

只有在执行tst.MoveNext()方法的时候, 才会调用for循环。因为IEnumerable是延迟加载的,每次访问的时候才会去取值。
如下例子比较了传统方式和yield的方式的不同:
static void Main(string[] args)
{
foreach (var item in FilterWithoutYield()) {
Console.WriteLine(item);//3,4,5
}
//可以用ToList()从IEnumerable<out T>创建一个List<T>,并获得长度3
Console.WriteLine(FilterWithoutYield().ToList().Count());
Console.ReadLine();
}
static List<int> Data() {
return new List<int> { 1, 2, 3, 4, 5 };
}
//这种传统方式需要额外创建一个List<int> 增加开销,而且需要把Data()全部加载到内存才能再遍历。
static IEnumerable<int> FilterWithoutYield() {
List<int> result = new List<int>();
foreach (int i in Data()) {
if (i > 2)
result.Add(i);
}
return result;
}
static IEnumerable<int> FilterWithYield() {
foreach (int i in Data()) {
if (i > 2)
yield return i;
}
yield break;
}
在lambda表达式中写的select, where都没有循环遍历,只有在调用ToList()或者foreach的时候才会从集合里面取值。
如果我们定义一个MyFunc委托:

然后在main函数中调用这个方法:

我们会发现在调用到MyFunc这一步时,MyFunc委托根本没有执行,只有到ToList()的时候才会执行。
迭代器代码使用yield return 语句依次返回每个元素,yield break将终止迭代。