在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 ;
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
public interface IEnumerator
object? Current { get; }
bool MoveNext();
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 ;
int position = -1 ;
public ArrEnumerator( string[] theArrs )
Arrs = new string[theArrs.Length] ;
for( int i = 0 ; i < theArrs.Length ; i++ )
Arrs[i] = theArrs[i] ;
public object Current
if( position == -1 )
throw new InvalidOperationException() ;
if( position >= Arrs.Length )
throw new InvalidOperationException() ;
return Arrs[position] ;
public bool MoveNext()
if( position < Arrs.Length - 1 )
position++ ;
return true ;
return false ;
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
public interface IEnumerable
IEnumerator GetEnumerator();
正如前面所说的 , IEnumerable接口只有一个成员 —— 方法 GetEnumerator ;
接下来 , 我们将通过下面的这段代码 ,演示可枚举类型的标准声明形式 ,或者说是标准模板:
using System.Collections ;
class MyColors : IEnumerable
string[] colors = { "Red" , "Yellow" , "Blue" } ;
public IEnumerator GetEnumerator()
return new ColorEnumerator(Colors) ;
复制代码