数组或集合实现带GetEumerator()方法的IEnumerable接口。GetEnumerator()方法返回一个实现IEumerator接口的枚举器。
注意:
GetEumerator()方法用IEnumerable接口定义。foreach语句并不真的需要在集合类中实现这个接口。有一个名为GetEumerator()的方法,它返回IEnumberator接口的对象就足够了。
IEnumberator接口
foreach语句使用IEnumberator接口的方法和属性,迭代集合中的所有元素。为此,IEnumberator定义了Current属性,来返回光标所在的元素,该接口的MoveNext()方法移动到集合的下一个元素上,如果这个元素存在,该方法就返回true。如果集合中不再有更多的元素,该方法就返回false。
这个接口的泛型版本IEnumberator<T>派生自IDisposable,因此定义了Dispose()方法,来清理给枚举器分配的资源。
foreach
foreach语句不会被解析为IL代码中的foreach语句,而会把它转换为IEnumerator接口的方法和属性。
foreach(var p in persions)
{
writeLine(p);
}
foreach语句会被解析为下面的代码段。首先,调用GetEnumerator()方法,获得数组的一个枚举器。然后在while循环中访问元素。
IEnumberator<Person> enumerator = persons.GEtEnumberator();
while(enumberator.MoveNext())
{
Person p = enumerator.Current;
WriteLine(p);
}
yield
yield语句用于创建枚举器。yield return语句返回集合的一个元素,并移动到下一个元素上。yield break停止迭代。
class HelloCollection
{
public IEnumerator<string> GetEnumerator()
{
yield return "Hello";
yield return "World";
}
}
static void Main()
{
var helloCollection = new HelloCollection();
foreach (string s in helloCollection)
{
WriteLine(s);
}
}
// 输出
Hello
World
注意
包含yield语句方法或属性也称为迭代块。迭代块必须声明为返回IEnumerator或IEnumerable接口,或者这些接口的泛型版本。这个块可以包含多个yield return语句或yield break语句,但不能包含return语句。
使用迭代块,编译器会生成一个yield类型,其中包含一个状态机,如下面的代码段所示。yield类型实现IEnumerator和IDisposable接口的属性和方法。在下面的例子中,可以把yield类型看作内部类Enumerator。外部类的GetEnumerator()方法实例化并返回一个新的yield类型。在yield类型中,变量state定义了迭代的当前位置,每次调用MoveNex()时,当前位置都会改变。在yield类型中,变量state封装了迭代块的代码,并设置了current变量的值,从而使Current属性根据 位置返回一个对象。
public class HelloCollection
{
public IEnuberator 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;
}
boid System.Collections.IEnumerator.Reset()
{
throw new NotSupportedException();
}
string System.Collections.Generic.IEnumerator<string>.Current => current;
object System.Collections.IEnumerator.Current => current;
void IDisposable.Dispose()
{
}
}
}