C#学习笔记 —— 枚举器和迭代器
1、枚举器和可枚举类型
(1)使用foreach语句
-
数组可以按需提供一个枚举器(enumerator)的对象.
-
枚举器可以一次返回请求中的数组
-
枚举器知道项的次序并且跟踪他在序列中的位置
-
对于有枚举器的类型而言, 必须有一种方法能够获取他
-
获取对象枚举器的方法是调用对象的
GetEnumerator
方法, 实现GetEnumerator
的类型叫做可枚举类型 -
数组是可枚举类型
-
-
foreach
结构设计用来和可枚举类型一起使用, 只要给他的遍历对象是可枚举类型, 比如数组, 他就会执行如下行为-
通过调用
GetEnumerator
方法来获取对象的枚举器 -
从枚举其中请求每一项并且把它作为迭代变量, 代码可读取该变量但是不能改变
-
foreach(Type VarName in 可枚举对象) { ... }
2、IEnumerator接口
实现了Ienumerator接口的枚举类包含三个函数成员
-
Current
-
返回序列中当前位置项的属性
-
他是只读属性
-
返回
object
类型的引用, 所以可以返回任何类型的对象
-
-
MoveNext
-
把枚举器位置前进到集合下一项的方法
-
返回布尔值, 指示新的位置是有效位置, 还是已经超过了序列的尾部
-
如果新的位置是有效的返回真, 否则代表当前位置到达了尾部返回假
-
枚举器的原始位置在序列的第一项之前, 因此
MoveNext
必须在第一次使用Current之前调用
-
-
Reset
重置为原始状态 -
枚举器跟踪序列中当前项的方式完全取决于实现, 可以通过对象引用、索引值或者其他方式来实现
-
对于内置的一维数组来说, 就使用项的索引
3、IEnumerable接口
-
可枚举类实现了
IEnumerable
接口的类 -
IEnumerable
接口只有一个成员,GetEnumerator
方法,它发挥对象的枚举器
using System.Collections; class MyClass : IEnumerable { public IEnumerator GetEnumerator {} }
使用IEnumerable、IEnumerator的示例
class ColorEnumerator : IEnumerator { string[] colors; int position = -1; public ColorEnumerator(string[] theColors) { colors = new string[theColors.Length]; for(int i = 0; i < theColors.Length; i++) { colors[i] = theColors[i]; } } //实现IEnumrator Current public object Current { get { if(position < 0 || position >= colors.Length) { throw new InvalidOperationException(); } return colors[position]; } } //实现MoveNext public bool MoveNext() { if(position < colors.Length - 1) { position++; return true; } else { return false; } } //实现重置 public void Reset() { position = -1; } }
class Spectrum : IEnumerable { string[] Colors = {"blue", "yellow", "green", "black"}; public IEnumerator GetEnumerator() { return new ColorEnumerator(Colors); } }
static void Main(string[] args) { Spectrum spectrum = new Spectrum(); foreach(string color in spectrum) { Console.WriteLine(color); } }
4、泛型枚举接口
-
对于非泛型接口形式
-
IEnumerable
接口的`GetEnumerator
方法返回实现IEnumerator
的枚举器类实例 -
实现
IEnumerator
的类实现了Current
属性, 他返回object
类型的引用,然后我们必须把他转换为对象的实际类型
-
-
泛型接口继承自非泛型接口, 对于泛型接口
-
IEumerable<T>
接口的GetEnumerator
方法返回实现IEnumator<T>
的枚举器的实例 -
Current
属性,返回实际类型的实例, 而不是返回object
类型的引用 -
这些是协变接口,所以他们的实际声明就是
IEumerable<out T>
,IEumerator<out T>
-
5、迭代器 using System.Collections.Generic;
-
迭代器是C#编译器为我们提供的更简单的创建枚举器和可枚举类型的方式
-
yield return
声明这是枚举的下一项
(1)迭代器块
-
迭代器块是有应该或多个
yield
语句的代码块 下面三种代码块中任意一种都可以-
方法主体
-
访问器主体
-
运算符主体
-
-
迭代器块与其他代码块不同
-
其他块包含的语句被当作是命令式的,先执行代码块的第一个语句,然后执行后面的语句,最后离开控制块
-
迭代器块不是需要在同一时间执行的一串命令式命令,而是声明性的,描述了希望编译器为我们创建的枚举器的行为,迭代器块中的代码描述了如何枚举元素
-
-
迭代器块有两个特殊语句
-
yield return
语句指定了序列中要返回的下一项 -
yield break
语句指定在序列中没有其他项
-
-
编译器得到有关如何枚举项的描述后,使用它来构建包含所有需要的方法和属性实现的枚举器类,产生的类被嵌套包含在声明迭代器的类中
//生产枚举器 public IEnumerator<string> IteratorMethod() { yield return... }
//生成可枚举类型 public IEnumerable<string> IteratorMethod() { yield return... }
(2)使用迭代器来创建枚举器
-
BlackAndWhite
是迭代器块,可以为MyClass
类产生返回枚举器的方法 -
MyClass
实现GetEnumerator()
,并且调用迭代器块,并且返回枚举器, -
MyClass
是可枚举类型,所以可以直接在主方法中使用foreach
,它不检查接口,只检查接口的实现
class MyClass { //实现GetEnumerator(),是可枚举类型 public IEnumerator<string> GetEnumerator() { return BlackAndWhite(); } //用迭代器产生枚举器 public IEnumerator<string> BlackAndWhite() { yield return "black"; yield return "gray"; yield return "white"; } }
static void Main(string[] args) { MyClass mc = new MyClass(); foreach (string color in mc) { Console.WriteLine(color); } }
(3)使用迭代器来创建可枚举类型
-
BlackAndWhite
迭代器方法返回IEnumerable<string>
可枚举对象 -
因此
MyClass
首先调用BlackAndWhite()
获取它的可枚举对象,然后调用对象的GetEnumerator()
来获取它的结果,从而实现GetEnumerator()
-
在主方法中,可以使用类的实例,也可以直接调用
BlackAndWhite()
因为他返回的是可枚举类型
class MyClass { public IEnumerator<string> GetEnumerator() { //获取可枚举类型 IEnumerable<string> myEnumerable = BlackAndWhite(); //获取枚举器 return myEnumerable.GetEnumerator(); } //用迭代器产生可枚举类型 public IEnumerable<string> BlackAndWhite() { yield return "black"; yield return "gray"; yield return "white"; } }
static void Main(string[] args) { MyClass mc = new MyClass(); //使用类对象 foreach (string color in mc) { Console.WriteLine($"{color}"); } //使用类枚举器方法 foreach (string color in mc.BlackAndWhite()) { Console.WriteLine($"{color}"); } }
6、常见迭代器模式
(1)枚举器迭代器模式
-
必须通过实现
GetEnumerator()
来让类实现可枚举,返回由迭代器返回的枚举器
class MyClass { public IEnumerator<XXX> GetEnumerator() { return IteratorMethod(); } public IEnumerator<XXX> IteratorMethod() { yield return xxx; } }
static void Main(string[] args) { MyClass mc = new MyClass(); //使用类对象 foreach (XXX xxx in mc) { Console.WriteLine($"{xxx}"); } }
(2)可枚举类型的迭代器模式
-
可以让类实现
GetEnumerator()
来让类本身可枚举,或不实现GetEnumerator()
让类不可枚举-
如果实现,让他调用迭代器方法一获取自动生成的实现
IEnumerable
的类实例,然后从IEnumerable
对象返回由GetIEnumerable()
创建的枚举器 -
如果不实现,类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法,也就是使用类枚举器方法
-
class MyClass { public IEnumerable<XXX> GetEnumerator() { return IteratorMethod().GetEnumerator(); } public IEnumerable<XXX> IteratorMethod() { yield return xxx; } }
static void Main(string[] args) { MyClass mc = new MyClass(); //使用类对象 foreach (XXX xxx in mc) { Console.WriteLine($"{xxx}"); } //使用类枚举器方法 foreach (XXX xxx in mc.IteratorMethod()) { Console.WriteLine($"{xxx}"); } }
7、产生多个可枚举类型
-
下列类有两个可枚举类型的迭代器, 从UV -> IR 和 IR -> UV
-
尽管有两个方法返回可枚举类型,类本身不是可枚举类型,没实现
GetEnumerator()
class Spectrum { string[] colors = {"blue", "green", "yellow"}; //产生可枚举类型 public IEnumerable<string> UVtoIR() { for(int i = 0; i < colors.Length; i++) { yield return colors[i]; } } //产生可枚举类型 public IEnumerable<string> IRtoUV() { for(int i = colors.Length - 1 ; i >= 0 ; i--) { yield return colors[i]; } } }
static void Main(string[] args) { Spectrum sp = new Spectrum(); //使用类枚举器方法 foreach (string color in sp.UVtoIR()) { Console.WriteLine($"{color}"); } //使用类枚举器方法 foreach (string color in sp.IRtoUV()) { Console.WriteLine($"{color}"); } }
8、将迭代器作为属性
-
使用迭代器来产生具有两个枚举器的类
-
演示迭代器如何能实现为属性而不是方法
class Spectrum { //根据这个属性来返回枚举器 bool _listFromUVtoIR; string[] colors = {"blue", "green", "yellow"}; //构造器 public Spectrum(bool listFromUVtoIR) { _listFromUVtoIR = listFromUVtoIR; } //产生枚举器 public IEnumerator<string> GetEnumerator() { return _listFromUVtoIR ? UVtoIR : IRtoUV; } public IEnumerator<string> UVtoIR() { for(int i = 0; i < colors.Length; i++) { yield return colors[i]; } } public IEnumerator<string> IRtoUV() { for(int i = colors.Length - 1 ; i >= 0 ; i--) { yield return colors[i]; } } }
static void Main(string[] args) { Spectrum spUV = new Spectrum(true); Spectrum spIR = new Spectrum(false); //使用类枚举器 foreach (string color in spUV) { Console.WriteLine($"{color}"); } //使用类枚举器 foreach (string color in spUV) { Console.WriteLine($"{color}"); } }
9、迭代器的实质
-
迭代器需要
using System.Collections.Generic;
命名空间 -
在编译器生成的枚举器,不支持
Reset
他是接口需要的方法,所以实现它会导致调用时抛出异常 -
在后台,由编译器生成的枚举器类是包含4个状态的状态机
-
Before
首次调用MoveNext
之前的初始状态 -
Running
调用MoveNext
后进入这个状态-
枚举器检测并设置下一项的位置,在遇到
yield return
yield break
或在迭代器体结束,退出状态
-
-
Suspended
等待下次调用MoveNext
的状态 -
After
没有更多项可以枚举的状态
-
-
如果状态机在
Before
或Suspended
状态时调用了MoveNext
方法,就转到了Running
状态,在Running
状态中,它检测集合的下一项并设置位置 -
如果由更多项会转入
Suspended
,如果没有,他转入并保持在After
状态
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律