02011902 枚举器和迭代器02-迭代器、代器创建枚举器或可枚举类型、迭代器作为属性
1. 迭代器
- 可枚举类和枚举器在.NET集合类类中被广泛使用,虽然我们已经知道如何创建自己的可枚举类型和枚举器了。但是C#从2.0版本开始提供了更加简单的创建枚举器和可枚举类型的方式。
- 编译器可以为我们创建它们,这种结构叫做迭代器。
- 程序员可以把手动编码的可枚举类型和枚举器替换为由迭代器生成的可枚举类型和枚举器。
// 形式1:通过方法声明实现了一个产生并返回枚举器的迭代器(yield return形式)返回泛型枚举器,这里返回字符串对象↓
public IEnumerator<string> BlackAndWhite() // 迭代器是一个方法,返回一个泛型的枚举器
{yield return "Black";yield return "Gray";yield return "White";
}说明:
1. 迭代器返回一个泛型枚举器,该枚举器返回三个string类型的项。
2. yield return语句声明这是枚举中的下一项。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 形式2:通过方法声明实现了一个产生并返回枚举器的迭代器(通过遍历数组 + yield return形式)
public IEnumerator<string> BlackAndWhite()
{string[] theColors = {"Black", "Gray", "White"};for(int i= 0; i < theColors.Length; i++)yield return theColors[i]; // yield return
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
总结:上述代码形式1和形式2完全等价。
1.1 迭代器块
- 迭代器块 → 是有一个或多个yield语句的代码块。下面3种类型的代码块中的任意一种都可以是迭代器块。
- 方法主体。
- 访问器主体。
- 运算符主体。
- 迭代器块与其他代码块不同,其他块包含的语句被当作时命令式的。
- 也就是说,先执行代码块的第一个语句,然后执行后面的语句,最后控制离开块。
- 迭代器块不需要再同一时间执行的一串命令式命令,而是声明性的。
- 迭代器块描述了希望编译器为我们床架你的枚举器类的行为。
- 迭代器块中的代码描述了如何枚举元素。
1.2 迭代器块的两个特殊语句
yield return → 语句指定了序列中要返回的下一项。
yield break → 语句指定在序列中没有其它项。
1.3 产生可枚举类型的迭代器
// 产生可枚举类型的迭代器
public IEnumerable<string> IteratorMethod()
{...yield return ...;
}
1.4 产生枚举器的迭代器
public IEnumerator<string> IteratorMethod()
{...yield return ...;
}
2. 使用迭代器来创建枚举器
using System;
using System.Collections.Generic;namespace Demo01
{class MyClass{public IEnumerator<string> GetEnumerator() // @2 实现GetEnumerator方法,返回枚举器{return BlackAndWrite(); // @3 调用返回枚举器的方法}public IEnumerator<string> BlackAndWrite() // @1 返回枚举器的方法{yield return "Black";yield return "Gray";yield return "White";}}class Program{static void Main(){MyClass mc = new MyClass();foreach (string shade in mc) // @4 使用MyClass的实例{Console.WriteLine(shade);}Console.ReadLine();}}
}控制台输出:
Black
Gray
White说明:
1. MyClass类在@1处的BlackAndWrite方法是一个迭代器块,可以为MyClass类产生返回枚举器的方法。
2. MyClass类在@2处实现GetEnumerator方法,在@3处调用BlackAndWrite方法并返回BlackAndWrite返回的枚举器。
3. MyClass类实现了GetEnumerator方法,是可枚举类型,所以可以在foreach语句中直接使用该类的实例。注意,foreach语句只检查接口的实现,不检查接口。
3. 使用迭代器来创建可枚举类型
using System;
using System.Collections.Generic;namespace Demo01
{class MyClass{public IEnumerator<string> GetEnumerator() // @2 实现GetEnumerator方法,返回枚举器{IEnumerable<string> myEnumerable = BlackAndWhite(); // @3 获取可枚举类型return myEnumerable.GetEnumerator(); // @4 获取枚举器}public IEnumerable<string> BlackAndWhite() // @1 返回可枚举类型的方法{yield return "Black";yield return "Gray";yield return "White";}}class Program{static void Main(){MyClass mc = new MyClass();foreach (string shade in mc){Console.WriteLine($"{shade}");}foreach (string shade in mc.BlackAndWhite()){Console.WriteLine($"{shade}");}Console.ReadLine();}}
}控制台输出:
Black
Gray
White
Black
Gray
White注意:
1. 在2小节示例创建的类包含两部分,一部分产生返回枚举器方法的迭代器,一部分实现返回枚举器的GetEnumerator方法。
2. 在本例中,使用迭代器来创建可枚举类型,而不是创建枚举器。
3. 需要体会到本例和之前代码的不同。说明:
1. 在2小节中,BlackAndWrite迭代器返回IEnumerator<string>,MyClass类通过返回由BlackAndWrite迭代器创建的对象来实现GetEnumerator方法。
2. 在本例中,BlackAndWrite迭代器返回IEnumerable<string>而不是IEnumerator<string>。
2.1 因此,MyClass首先调用BlackAndWrite方法获取他的可枚举类型对象myEnumerable。
2.2 然后通过myEnumerable对象的GetEnumerator方法来获取它的结果,从而实现了GetEnumerator方法。
3. 重点,在foreach语句中我们可以使用类的实例,也可以通过实例直接调用BlackAndWhite方法。※※※
4. 产生多个可枚举类型
using System;
using System.Collections.Generic;namespace Demo01
{class MyClass{string[] colors = { "Black", "Gray", "White" };public IEnumerable<string> Func01() // 返回可枚举类型1{for (int i = 0; i < colors.Length; i++)yield return colors[i];}public IEnumerable<string> Func02() // 返回可枚举类型2{for (int i =colors.Length - 1; i >= 0; i--)yield return colors[i];}}class Program{static void Main(){MyClass mc = new MyClass();// foreach (string shade in mc) // 错误写法,因为MyClass类没有实现GetEnumerator方法,因此不能遍历对象。foreach (string shade in mc.Func01()) // 正确,通过方法来调用。{Console.WriteLine($"{shade}");}foreach (string shade in mc.Func02()) // 正确,通过方法来调用。{Console.WriteLine($"{shade}");}Console.ReadLine();}}
}控制台输出:
Black
Gray
White
White
Gray
Black说明:类本身不是可枚举类型,不能使用foreach来变量类的对象。
5. 将迭代器作为属性
using System;
using System.Collections.Generic;namespace Demo01
{class MyClass{bool __listFromUVtoIR;string[] colors = { "Black", "Gray", "White" };public MyClass(bool listFromUVtoIR){__listFromUVtoIR = listFromUVtoIR;}public IEnumerator<string> GetEnumerator(){return __listFromUVtoIR ? UVtoIR : IRtoUV;}public IEnumerator<string> UVtoIR // 只读属性{get{for (int i = 0; i < colors.Length; i++)yield return colors[i];}}public IEnumerator<string> IRtoUV // 只读属性{get{for (int i = colors.Length-1; i >= 0; i--)yield return colors[i];}}}class Program{static void Main(){MyClass mcUV = new MyClass(true);MyClass mcIR = new MyClass(false);foreach (string color in mcUV) Console.WriteLine($"{color}");foreach (string color in mcIR)Console.WriteLine($"{color}");Console.ReadLine();}}
}控制台输出:
Black
Gray
White
White
Gray
Black
6. 迭代器的实质

迭代器状态机
- 迭代器需要System.Colledctions.Generic命名空间,因此我们需要使用using指令引入它。
- 在编译器生成的枚举器中,不支持Rest方法。
- Rest是接口需要的方法,所以实现了它,但是调用时总是抛出System.NotSupportedException异常。
- 在后台,由编译器生成的枚举器类是包含4个状态的状态机。
- Before → 首次调用MoveNext之前的初始状态。
- Running → 调用MoveNext后进入这个状态。在这个状态中,枚举器检测并设置下一项的位置。
- 在遇到yield return,yield break或在迭代器体结束时,退出状态。
- Suspended → 状态机等待下次调用MoveNext的状态。
- After → 没有更多项可以枚举的状态。
- 如果状态机在Before或Suspended状态时调用了MoveNext方法,就转到了Running状态。
- 在Running状态中,它检测集合的下一项并设置位置。
- 如果有更多项,状态机会进入Suspended状态;如果没有更多项,它转入并保持在After状态。
结尾
书籍:C#图解教程
著:【美】丹尼尔 · 索利斯;卡尔 · 施罗坦博尔
译:窦衍森;姚琪琳
ISBN:978-7-115-51918-4
版次:第5版
发行:人民邮电出版社
※敬请购买正版书籍,侵删请联系85863947@qq.com※
※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※