前言:
- c# 1.0以来,foreach就可以直接用来迭代数组和集合。
- 但对于一个自定义类,要用foreach来迭代的话,需要创建枚举器Enumerator, yield语句就是用来方便创建枚举器的。
- c# 2.0添加了yield语句。
foreach遍历X类型的原理:
- X类型,实现了
IEnumerable接口 IEnumerable接口定义了一个方法GetEnumerator()GetEnumerator()方法返回一个实现了IEnumerator接口的类型实例foreach就可以使用这个实现了IEnumerator接口的类型实例来遍历X类型了
虽然有上述内容,但是要用
foreach来遍历X类型,实际上并不强制要求在X类中必须实现IEnumerable接口。而是,只要在X类中有GetEnumerator()方法,就足矣。
这个
GetEnumerator()方法,照例,还是返回一个实现了IEnumerator接口的类型实例。重要的是,X类得有一个GetEnumerator()方法
IEnumerator接口
- 定义了
Current属性, 是当前元素 - 定义了
MoveNext(),移动到下一个元素- 如果有这个元素,则返回
true - 如果不再有更多元素,则返回
false
- 如果有这个元素,则返回
- 其泛型类,定义了
Dispose()
foreach,会被编译器进一步转换成IEnumerator接口的方法和属性:
如下例:遍历一个自定义类Person为元素的数组:
foreach(var person in persons)
{
Console.WriteLine(person);
}
会被c#编译器解析为:
IEnumerator<Person> enumerator = persons.GetEnumerator();
while(enumerator.MoveNext())
{
Person person = enumerator.Current;
Console.WriteLine(person);
}
yeild 语句
- yield return语句,返回集合的一个元素,并移动到下一个元素上。
- yield break语句,停止迭代。
public class HelloCollection
{
public IEnumerator<string> GetEnumerator()
{
yield return "Hello";
yield return "World";
}
}
public void HelloWorld()
{
var helloCollection = new HelloCollection();
foreach(var s in helloCollection)
{
Console.WriteLine(s);
}
}
以下代码被称作,迭代块。
public IEnumerator<string> GetEnumerator()
{
yeild return "Hello";
yeild return "World";
}
迭代块
包含yield 语句的方法或属性,被称为迭代块
迭代块必须声明为返回IEnumerator或IEnumerable接口(或它们的泛型版本)
迭代块可包含多条yield return和yield break语句,但不能包含return语句
迭代块,会被编译器进一步转换成,内部Yield类型
public class HelloCollection
{
public IEnumerator<string> GetEnumerator()
{
yield return "Hello";
yield return "World";
}
}
转换:
- 编译器会生成一个yield类型,如下是Enumerator类
- HelloCollection的GetEnumerator()方法会返回一个Enumerator类的实例
Enumerator类
- 是HelloCollection类的内部类
- 实现了
IEnumerator和IDisposable接口 - 包含一个状态机,
_state - MoveNext()方法 封装了迭代块的内容,并设置
_current值
public class HelloCollection
{
public IEnumerator GetEnumerator() => new Enumerator(0);
public class Enumerator: IEnumerator<string>, IEnumerator, IDisposable
{
private int _state;
private string _current;
public Enumerator(int state) => _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();
string System.Collections.IEnumerator<string>.Current => _current;
object System.Collections.IEnumerator.Current => _current;
void IDisposable.Dispose() { }
}
}
yield使用案例 - 遍历方式的优雅封装
public class MusicTitles
{
string[] names = {"Tubular Bells", "Hergest Ridge", "Ommadawn", "Platinum"};
public IEnumerator<string> GetEnumerator()
{
for (int i = 0; i < 4; i++)
{
yield return names[i];
}
}
public IEnumerable<string> Reverse()
{
for (int i = 3; i >= 0; i--)
{
yield return names[i];
}
}
public IEnumerable<string> Subset(int index, int length)
{
for (int i = index; i < index + length; i++)
{
yield return names[i];
}
}
}
var titles = new MusicTitles();
foreach(var title in titles)
{
Console.WriteLine(title);
}
Console.WriteLine();
Console.WriteLine("reverse");
foreach(var title in titles.Reverse())
{
Console.WriteLine(title);
}
Console.WriteLine("subset");
foreach(var title in titles.Subset(2,2))
{
Console.WriteLine(title);
}
yield 使用案例 - 轮流下棋
public class GameMoves
{
private IEnumerator _cross;
private IEnumerator _circle;
public GameMoves()
{
_cross = Cross();
_circle = Circle();
}
private int _move = 0;
const int MaxMoves = 9;
public IEnumerator Cross()
{
while(true)
{
Console.WriteLine($"Cross, move {_move}");
if (++_move >= MaxMoves)
{
yield break;
}
yield return _circle;
}
}
public IEnumerator Circles()
{
while(true)
{
Console.WriteLine($"Circle, move {_move}");
if (++_move >= MaxMoves)
{
yield break;
}
yield return _cross;
}
}
}
var game = new GameMoves();
IEnumerator enumerator = game.Cross();
while(enumerator.MoveNext())
{
enumerator = enumerator.Current as IEnumerator;
}