前言
问:什么是迭代器 ?
答:在C# 中,迭代器是一种设计模式,它允许一个类或集合(比如数组、列表或字典)的实例提供一种遍历其元素的方式。在C#2时引入的迭代器,来简化这一过程。
在C#中有少数接口是有特定的语言来支持,如IDisposable 接口就由Using语句来获得支持,其中IEnumerable就由foreach来支持。本文就围绕着当前两个接口来介绍我们的示例。
迭代器的介绍
迭代器是包含迭代器块,通常是方法或者是属性。迭代器通常返回类型都是IEnumerable或IEnumerable<T>类型的方法或属性,每个迭代器都有一个生成类型,返回类型是非泛型时则为Object类型,是泛型时则是返回泛型的类型实例类型。如IEnumerable<int>则返回的类型就是int。yield return 在迭代器中代表返回某一个值 如 yield return 1就是返回1这个数值。 yield break 则代表终止迭代器的进行。
迭代器的执行逻辑
通常迭代器是在foreach语句当中调用的,在foreach语句当中我们对一个非泛型集合进行迭代。执行顺序:
1:调用返回类型为GetEnumerator类型的方法
2:返回一个IEnumerator的引用,调用IEnumerator接口中的方法MoveNext()
3:通过当前方法的返回值来判断是否有值 如果有值(返回 True)则获取当前接口中的属性Current将其返回。
4:在返回True的情况下继续调用MoveNext方法循环,直到当前方法返回false代表当前已经不存在值的情况下结束当前迭代。
创建一个迭代器
创建自定义的迭代器通常涉及定义一个返回IEnumerator<T>
或IEnumerator
接口的方法,并在其中实现遍历集合的逻辑。
举个例子(目标框架不是framework 是.Net 8):
foreach (int i in new MyCollection())
{Console.WriteLine(i);
}
public class MyCollection
{private int[] items = { 1, 2, 3, 4, 5 };public IEnumerator<int> GetEnumerator(){foreach (var item in items){yield return item; // 使用 yield return 返回每个元素 }}
}
示例解释: 当前通过yield return来返回迭代中的值,迭代当前类输出结果为 1 2 3 4 5
模拟实现迭代器的执行逻辑
举个例子:
new MyCollection();
public class MyCollection
{private int[] items = { 1, 2, 3, 4, 5 };public IEnumerable<int> GetEnumerator(){foreach (var item in items){yield return item; // 使用 yield return 返回每个元素 }}public MyCollection(){IEnumerable<int> IEnumerators = GetEnumerator();using (IEnumerator<int> enmerator = IEnumerators.GetEnumerator()){while (enmerator.MoveNext()) {int item = enmerator.Current;Console.WriteLine(item);}}}
}
示例解释:与上述迭代器的执行逻辑描述的执行顺序是一样的。
迭代器是延迟执行
为什么说迭代器是延时执行?
因为当迭代器第一次调用MoveNext()方法,实际上返回时当前这个方法仿佛被暂停了,生成的代码会追踪当前语句执行进度。如我们在循环当中返回一些变量,每当我们返回一次那么我们当前的这个方法就会被暂停直到下一次调用MoveNext它就会在上次暂停的地方继续执行。
简单来说,迭代器在执行时采用延时执行的方式。当迭代器第一次调用MoveNext()
方法时,它会执行到yield return
语句,并返回当前的值。在这个过程中,迭代器的状态被暂停,并记录下当前执行的位置。下次调用MoveNext()
方法时,迭代器会从上次暂停的位置继续执行,返回下一个值。
这种延时执行的机制使得迭代器可以高效地处理大量数据或者耗时的操作。它不需要一次性生成所有的结果,而是按需逐个生成结果,减少了内存占用和计算开销。
所以可以说迭代器是一种延时执行的机制,它能够提供有效的迭代操作和节省资源的优势。
利用延迟执行这一特性来验证一下
举个例子(目标框架不是framework 是.Net 8):
foreach (var i in new S().GetEnumerator())
{Console.WriteLine(i);
}
public class S
{public IEnumerable<int> GetEnumerator(){try{yield return 1;yield return 2;yield return 3;yield return 4;}finally{Console.WriteLine("5");}}}
示例解释:
当前代码块就是输出结果顺序就是 1 2 3 4 5 很明显就是前面我们提到的记录当前位置后继续执行 也就是说并没有重新运行。
问:如果我们提前跳出会不会就不会执行 finally了而直接结束当前迭代了?
答:不会,会在结束之前调用finally代码块。
举个例子(修改上述部分代码)
public IEnumerable<int> GetEnumerator(){try{yield return 1;yield break;}finally{Console.WriteLine("5");}}
输出结果:1 5
问:为什么说finally因为在迭代器中结合?
答:使用finally代码块可以执行在结束迭代后的释放工作(如IO流的操作)
补充:
1 如果大家想了解MoveNext方法的实现逻辑可以网上查询这里不做介绍。
2 如有解释错误欢迎大家指出,谢谢大家😊