C# 1.0使用foreach语句可以轻松地迭代集合。在C# 1.0中,创建枚举器仍需要做大量的工作。C# 2.0添加了yield语句,以便于创建枚举器。
yield return语句返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代。
下面的例子是用yield return语句实现一个简单集合的代码。类HelloCollection包含GetEnumerator()方法。该方法的实现代码包含两个yield return语句,它们分别返回字符串Hello和World。
- using System;
 - using System.Collection;
 - namespace Wrox.ProCAharp.Arrays
 - {
 - public class HelloCollection
 - {
 - public IEumerator GetEumerator()
 - {
 - yield return "Hello";
 - yield return "World";
 - }
 - }
 - }
 
包含 yield语句的方法或属性也称为迭代块。迭代块必须声明为返回 IEnumerator或
IEnumerable接口。这个块可以包含多个yield return语句或yield break语句,但不能包含
return语句。
现在可以用foreach语句迭代集合了:
- public class Program
 - {
 - HelloCollection helloCollection = new HelloCollection();
 - foreach (string s in helloCollection)
 - {
 - Console.WriteLine(s);
 - }
 - }
 - }
 
使用迭代块,编译器会生成一个 yield 类型,其中包含一个状态机,如下面的代码所
示。yield 类型执行IEnumerator和IDisposable接口的属性和方法。在下面的例子中,可以
把yield 类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新
的yield 类型。在yield 类型中,变量state定义了迭代的当前位置,每次调用MoveNext()
时,当前位置都会改变。MoveNext()封装了迭代块的代码,设置了 current 变量的值,使
Current属性根据位置返回一个对象。
- public class HelloCollection
 - {
 - public IEnumerator GetEnumerator()
 - {
 - Enumerator enumerator = new Enumerator();
 - return enumerator;
 - }
 - public class Enumerator : IEnumerator, IDisposable
 - {
 - private int state;
 - private object current;
 - public Enumerator(int state)
 - {
 - this.state = state;
 - }
 - bool System.Collections.IEnumerator.MoveNext()
 - {
 - switch (state){
 - case 0:
 - current = "Hello";
 - state = 1;
 - return true;
 - case 1:
 - current = "World";
 - state = 2;
 - return true;
 - case 2:
 - break;
 - }
 - return false;
 - }
 - void System.Collections.IEnumerator.Reset()
 - {
 - throw new NotSupportedException();
 - }
 - object System.Collections.IEnumerator.Current
 - {
 - get
 - {
 - return current;
 - }
 - }
 - void IDisposable.Dispose()
 - {
 - }
 - }
 - }
 
现在使用yield return语句,很容易实现允许以不同方式迭代集合的类。类MusicTitles可以用默认方式通过 GetEnumerator()方法迭代标题,用 Reverse()方法逆序迭代标题,用Subset()方法搜索子集:
- public class MusicTitles
 - {
 - string[] names = {
 - "Tubular Bells", "Hergest Ridge",
 - "Ommadawn", "Platinum");
 - public IEnumerator GetEnumerator()
 - {
 - for (int i = 0; i < 4; i++)
 - {
 - yield return names[i];
 - }
 - }
 - public IEnumerable Reverse()
 - {
 - for (int i = 3; i >= 0; i–)
 - {
 - yield return names[i];
 - }
 - }
 - public IEnumerable Subset( int index, int length)
 - {
 - for (int i = index; i < index + length; i++)
 - {
 - yield return names[i];
 - }
 - }
 - }
 
迭代字符串数组的客户代码先使用GetEnumerator()方法,该方法不必在代码中编写,
因为这是默认使用的方法。然后逆序迭代标题,最后将索引和要迭代的元素个数传送给
Subset()方法,来迭代子集:
- MusicTitles titles = new MusicTitles();
 - foreach(string title in titles)
 - {
 - ConsoleWriteLine(title);
 - }
 - ConsoleWriteLine();
 - ConsoleWriteLine("reverse");
 - foreach(string title in titles.Reverse())
 - {
 - ConsoleWriteLine(title);
 - }
 - ConsoleWriteLine();
 - ConsoleWriteLine("subset");
 - foreach(string title in titles.Subset(2, 2))
 - {
 - ConsoleWriteLine(title);
 - }
 
使用yield语句还可以完成更复杂的任务,例如从yield return中返回枚举器。
在TicTacToe游戏中有9个域,玩家轮流在这些域中放置“十”字或一个圆。这些移
动操作由GameMoves类模拟。方法Cross()和Circle()是创建迭代类型的迭代块。变量cross
和circle在GameMoves类的构造函数中设置为Cross()和Circle()方法。这些域不设置为调
用的方法,而是设置为用迭代块定义的迭代类型。在Cross()迭代块中,将移动操作的信息
写到控制台上,并递增移动次数。如果移动次数大于9,就用yield break停止迭代;否则,
就在每次迭代中返回yield类型cross的枚举对象。Circle()迭代块非常类似于Cross()迭代块,
只是它在每次迭代中返回circle迭代类型。
- public calss GameMoves
 - {
 - private IEnumerator cross;
 - private IEnumerator circle;
 - public GameMoves()
 - {
 - cross = Cross();
 - circle = Circle();
 - }
 - private int move = 0;
 - public IEnumerator Cross()
 - {
 - while (true)
 - {
 - Console.WriteLine("Cross, move {0}", move);
 - move++;
 - if (move > 9)
 - yield break;
 - yield return circle;
 - }
 - }
 - public IEnumerator Circle()
 - {
 - while (true)
 - {
 - Console.WriteLine("Circle, move {0}", move);
 - move++;
 - if (move > 9)
 - yield break;
 - yield return cross;
 - }
 - }
 - }
 
在客户程序中,可以以如下方式使用GameMoves类。将枚举器设置为由game.Cross()返回的枚举器类型,以设置第一次移动。enumerator.MoveNext调用以迭代块定义的一次迭代,返回另一个枚举器。返回的值可以用Current属性访问,并设置为enumerator变量,用于下一次循环:
- GameMoves game = new GameMoves();
 - IEnumerator enumerator = game.Cross();
 - while (enumerator.MoveNext())
 - {
 - enumerator = (IEnumerator) enumerator.Current;
 - }
 
这个程序的输出会显示交替移动的情况,直到最后一次移动:
- Cross, move 0
 - Circle, move 1
 - Cross, move 2
 - Circle, move 3
 - Cross, move 4
 - Circle, move 5
 - Cross, move 6
 - Circle, move 7
 - Cross, move 8
 
5.7 小结
本章介绍了创建和使用简单数组、多维数组和锯齿数组的C#记号。Array类在C#数组
的后台使用,这样就可以用数组变量调用这个类的属性和方法。
我们还探讨了如何使用IComparable和IComparer接口给数组中的元素排序,描述了
用Array类执行的IEnumerable、ICollection和IList接口的特性,最后论述了C# 2.0中新
增的yield语句的优点。
数组和相关主题的更多信息,可参阅如下章节:第6章介绍了运算符和强制类型转换,
其中探讨了定制索引器的创建。第7章讨论了委托和事件。Array类的一些方法将委托用
作参数。第10章介绍了本章探讨的集合类。集合类有较灵活的尺寸,第10章还介绍了其
他容器,如字典和链表。
============ 欢迎各位老板打赏~ ===========
			
		与本文相关的文章
- · The instance of entity type ‘Customer’ cannot be tracked because another instance with the same key value for {‘Id’} is already being tracked.
 - · .NET8实时更新nginx ip地址归属地
 - · 解决.NET Blazor子组件不刷新问题
 - · .NET8如何在普通类库中引用 Microsoft.AspNetCore
 - · .NET8 Mysql SSL error
 - · ASP.NET Core MVC的Razor视图渲染中文乱码的问题
 - · .NETCORE 依赖注入服务生命周期
 - · asp.net zero改mysql
 - · .NET5面试汇总
 - · .Net连接Mysql数据库的Convert Zero Datetime日期问题
 - · vue使用element-ui中的Message 、MessageBox 、Notification
 - · Asp.Net Core Filter 深入浅出的那些事-AOP
 
