C# - 枚举器和迭代器
# 前述
在C#里 , foreach语句可以用来遍历数组中的元素 ,如下所示 :
int[] arr1 = { 10 , 11 , 12 , 13} ; //定义数组
foreach ( int item in arr ) //枚举元素
Console.WtitLine( $"Item value : {item}" );
这段代码将会产生如下的输出 :
Item value: 10
Item value: 11
Item value: 12
Item value: 13
你应该感到好奇 —— 为什么foreach语句可以自动遍历数组中的每一个元素 ? 要回答这个问题 ,我们得先明确 : foreach语句只是一种语法糖 ,或者更通俗的说 —— 它只是一种方便了程序员的简化写法而已 。
所以 ,上述代码的等价非简化写法是 :
static void Main()
{
int[] arr1 = { 10 , 11 , 12 , 13 } ; //创建数组
//获取数组对象的枚举器
IEnumerator ie = arr1.GetEnumerator();
//移到下一项
while( ie.MoveNext() )
{
int item = (int)ie.Current ; //ie.current默认是Object类型,转型为int ;
Console.WriteLine($"Item value: {item}"); //输出
}
}
这段代码的输出 ,和上述使用foreach语法糖的代码相一致 :
Item value: 10
Item value: 11
Item value: 12
Item value: 13
这段代码才是foreach语法糖的实质 ,也就是说 ,在经过C#编译器的编译之后 ,foreach语句会被编译成这种形式的代码 ,而这也是程序运行期间具体被执行的代码 ;
由上述代码可以看出 ,使用foreach语句遍历数组的本质是 : 通过调用数组对象的GetEnumerator()方法可以获取一个叫做枚举器的对象(实现了IEnumerator接口的类的实例) ,通过操纵这个对象 , 可以依次序的控制枚举器所迭代的数组元素 。而数组类型的getEnumerator()方法是通过继承并且实现IEnumerable接口而来的,我们把实现了IEnumerable接口的类叫做可枚举类型 ,例如数组类就是可枚举类型 ;
我们从上述对foreach的探讨 ,引出了两个全新的概念 —— 枚举器和可枚举类型 ,这也是本文所要探讨的焦点 ,接下来 , 我们将通过一系列的示例 ,来探讨它们的实现原理 和运作机制 ;
#枚举器的本质 - 实现IEnumerator接口的类的实例
在上文中 ,我们提到枚举器是实现了IEnumerator接口的类的实例 ,所以我们来看一下IEnumerator接口长啥样 :
namespace System.Collections
{
//
// 摘要:
// Supports a simple iteration over a non-generic collection.
public interface IEnumerator
{
//
// 摘要:
// Gets the element in the collection at the current position of the enumerator.
//
// 返回结果:
// The element in the collection at the current position of the enumerator.
object? Current { get; }
//
// 摘要:
// Advances the enumerator to the next element of the collection.
//
// 返回结果:
// true if the enumerator was successfully advanced to the next element; false if
// the enumerator has passed the end of the collection.
//
// 异常:
// T:System.InvalidOperationException:
// The collection was modified after the enumerator was created.
bool MoveNext();
//
// 摘要:
// Sets the enumerator to its initial position, which is before the first element
// in the collection.
//
// 异常:
// T:System.InvalidOperationException:
// The collection was modified after the enumerator was created.
void Reset();
}
}
正如上述代码显示的 ,IEnumerator接口包含了3个需要被继承它的类所实现的函数成员 ,分别是 :
- Current : 它是一个只读的属性 , 它返回的是object类型的引用 ,所以可以返回任何类型的对象 ,或者通俗点说 —— Current返回序列中的当前位置项
- MoveNext : 把枚举器位置前进到集合中下一项的方法 。它返回布尔值 ,指示新的位置是有效位置还是已经超过了序列的尾部 ,即 ,如果新的位置是有效的 ,方法返回true ,如果是无效的(比如当前位置到达了尾部),方法返回false 。因为枚举器的原始位置在序列的第一项之前 ,因此MoveNext必须在第一次使用Current之前调用 ;
- Reset : 把位置重置为原始状态的方法 ;
下面展示一个枚举器类ArrEnumerator ,可以把它看成枚举器类的标准模板, 其继承并且实现了接口IEnumerator :
using System ;
using System.Collections ;
class ArrEnumerator : IEnumerator
{
string[] Arrs ; // 这个Arrs是关键,通过它保存将要被枚举器迭代的序列的副本(其实就是数组) 。 作为演示 ,这里把它置为string[]类型 ,它也可以是int[]、float[]或者其它。
int position = -1 ; // 序列(数组元素)位置 ,默认是-1 ;
//构造函数
public ArrEnumerator( string[] theArrs ) // theArrs是需要被迭代的序列(数组)对象的引用
{
// 通过Arrs保存副本 ;注意 : Arrs和theArrs是对不同序列对象的引用
Arrs = new string[theArrs.Length] ;
for( int i = 0 ; i < theArrs.Length ; i++ )
{
Arrs[i] = theArrs[i] ;
}
}
//实现Current , 返回枚举器所指向的当前序列元素 ,返回的是一个object类型的引用
public object Current
{
get
{
if( position == -1 )
throw new InvalidOperationException() ;
if( position >= Arrs.Length )
throw new InvalidOperationException() ;
return Arrs[position] ;
}
}
//实现MoveNext() ,以便移动到序列的下一个位置
public bool MoveNext()
{
if( position < Arrs.Length - 1 )
{
position++ ;
return true ;
}
else
{
return false ;
}
}
// 实现reset ,将枚举器恢复到初始状态
public void reset()
{
position = -1 ;
}
}
上述代码描述了枚举器类的标准模板,虽然对IEnumerator接口成员的具体实现方式可能不同 。
我们可以从上述代码看出 ,枚举器是有状态的 , 这个状态是用position来描述的 ,position的值不同 , 我们便说它的状态是不同的 。而状态的切换 , 即position值的+1递增 , 是由MoveNext()方法来实现的 。每次MoveNext()之后 , 只要position< arrs.Lenght ,那么新的位置(状态)就是有效的 ,moveNext便会返回true 。否则 , 如果移动到的新的位置(状态)是无效的 ,即position >= arrs.Length , 那么就会返回false ;当状态为 position == arrs.Length时 ,继续调用moveNext()将总会返回false ,此时,如果在调用Current将抛出异常 。
# 可枚举类型的本质 - 实现了IEnumerable接口
可枚举类是指实现了IEnumerable接口的类 。IEnumerable接口只有一个成员 —— GetEnumerator方法 , 它负责返回对象的枚举器 ;
下面的这段代码是IEnumerable接口的具体实现 :
namespace System.Collections
{
//
// 摘要:
// Exposes an enumerator, which supports a simple iteration over a non-generic collection.
public interface IEnumerable
{
//
// 摘要:
// Returns an enumerator that iterates through a collection.
//
// 返回结果:
// An System.Collections.IEnumerator object that can be used to iterate through
// the collection.
IEnumerator GetEnumerator();
}
}
正如前面所说的 , IEnumerable接口只有一个成员 —— 方法 GetEnumerator ;
接下来 , 我们将通过下面的这段代码 ,演示可枚举类型的标准声明形式 ,或者说是标准模板:
using System.Collections ;
class MyColors : IEnumerable
{
string[] colors = { "Red" , "Yellow" , "Blue" } ;
public IEnumerator GetEnumerator()
{
return new ColorEnumerator(Colors) ;
}
}
本文转自 https://juejin.cn/post/7019678517482749988,如有侵权,请联系删除。