foreach
对于数组来说,只依赖于其Length属性和索引操作符[]
对于不定长集合而言,需要iterator参与其中
IEnumerable

IEnumerable<T>: 需要实现GetEnumerator方法,返回一个唯一迭代器
IEnumerator: 实现迭代器
引入IEnumerable<T>的原因是:保证每次使用的迭代器(IEnumerator)是一个新的对象,状态完全独立,解决状态冲突,支持多线程遍历
由于迭代器需要清除状态,故继承IDisposable接口,在退出迭代或迭代结束的时候会自动调用(无非托管资源的时候也无所谓)
yield
简化集合或系列的遍历
yield return : 更新保存状态,返回下一个迭代元素对于返回值为
IEnumerable<T> / IEnumerator<T>每次「生成(yield)」一个T类型的值这两种返回值类型的区别就是这两个接口的特性区别:
IEnumerable<T>返回的是一个可迭代的集合,支持foreach的多次遍历(每次遍历调用GetEnumerator())
IEnumerator<T>返回的就一个枚举数对象,仅支持单次遍历,可以手控遍历过程
// T must be the yield return value public static IEnumerable<int> GetEvenNumbers(int n) { int cur = -2; while (cur < n) { cur += 2; yield return cur; } } foreach (var e in GetEvenNumbers(10)) { Console.WriteLine(e); }
yield break : 立即终止迭代,不返回任何东西yield工作原理:生成一个状态机,维护迭代器的方法执行状态
yield return不会触发finally块,yield break或者外部显式终止迭代时才会
yield语句的要求:
- 只能在方法、用户自定义操作符、索引器、属性的get访问器方法中出现,成员不可获取任何ref和out参数
- 不能在匿名函数(包括匿名方法和Lambda表达式中)出现
- 不能在catch和finally中出现,并且出现在try中时不允许有catch块
Iterator原理
C#2.0引入的,通过yield instructions来简化两个IEnumxxx的使用(枚举数模式)
C#编译器遇到迭代器的时候,会把它的内容扩展成实现了 枚举数模式 的CIL代码
所以Iterator不依赖于运行时,并且性能不会提高
实际工作原理:编译器创建一个嵌套的私有类来实现
IEnumerator<T> 接口来处理迭代器语法冷知识:yield是「上下文关键字」,不是「保留关键字」,所以你可以用yield作为标识符,但显然很蠢