【下载FastReport.Net最新版本】

我们都知道foreach循环是 - 循环遍历集合的所有元素。它在易用性方面的最大优势 - 我们不需要担心集合中有多少元素。然而,许多人不知道这只是语法“sugar”,这有利于程序员的工作。因此,我们只需知道生成的编译器将转换的内容。foreach循环的工作方式不同,具体取决于您要排序的集合。

(1)如果必须处理平凡阵列,我们总能知道它的长度。因此,foreach最终将转换为for循环。例如:

int[] array = new int[]{1, 2, 3, 4, 5, 6};
foreach (int item in array)
{
 Console.WriteLine(item);
}

编译器将循环转换为此构造:

int[] temp;
int[] array = new int[]{1, 2, 3, 4, 5, 6};
temp = array;
for (int i = 0; i < temp.Length; i++)
{
 int item = temp[i];
 Console.WriteLine(item);
}

(2)但是,许多集合不支持对元素的索引访问,例如:Dictionary,Queue,Stack。在这种情况下,将使用迭代器模板。此模板基于接口System.Collections.Generic.IEnumerator 和非通用System.Collections.IEnumerator,它允许您迭代集合中的元素。IEnumerator包含:

  • MoveNext()方法 - 将枚举数移动到集合的下一个元素;
  • Reset()方法 - 重新启动枚举,将枚举数设置为起始位置;
  • Current属性 - 返回集合的当前元素。

IEnumirator 继承自两个接口 - IEnumirator和IDisposable。它包含Current属性的重载,按类型提供其实现。既然我们提到了接口IDisposable,那么我们就会说几句话。它包含释放资源所需的唯一Dispose()方法。每次循环终止或退出时,IEnumirator 都会清除资源。我们来看看这个循环:

System.Collections.Generic.Queue<int> queue = new System.Collections.Generic.Queue<int>();
 queue.Enqueue(1);
 queue.Enqueue(2);
 queue.Enqueue(3);
 
 foreach (int item in queue)
 {
 Console.WriteLine(item);
 }

编译器将其转换为类似的代码:

System.Collections.Generic.Queue<int> queue = new System.Collections.Generic.Queue<int>();
 queue.Enqueue(1);
 queue.Enqueue(2);
 queue.Enqueue(3);
 
int num;
while (queue.MoveNext())
{
 num = queue.Current;
 Console.WriteLine(num);
}

在此示例中,MoveNext替换了在循环期间计算元素的需要。当它没有收到下一个元素时,它返回fasle并且循环终止。 但是,尽管如此,这段代码只是近似于编译器的真正产生。问题是,如果您有两个或更多重叠循环使用相同的集合,则每个MoveNext调用将影响所有循环。这个过程不适合任何人。想出了第二个接口IEnumirator。

它包含唯一的方法GetEnumerator(),它返回一个枚举器。因此,IEnumerable 及其IEnumerable的通用版本允许您呈现枚举集合类中的元素的逻辑。通常,这是一个嵌套类,可以访问集合的元素并支持IEnumerator 。拥有每个枚举器,不同的消费者不会相互干扰,同时执行集合的枚举。 因此,上面的例子应该考虑两点 - 获得一个枚举器和释放资源。以下是编译器实际转换foreach循环代码的方法:

System.Collections.Generic.Queue<int> queue = new System.Collections.Generic.Queue<int>();
 System.Collections.Generic.Queue<int>.Enumerator enumirator;
 
 IDisposable disposable;
 
 enumirator = queue.GetEnumerator();
 queue.Enqueue(1);
 queue.Enqueue(2);
 queue.Enqueue(3);
 
 try
 {
 int num;
 while (enumirator.MoveNext())
 {
 num = enumirator.Current;
 Console.WriteLine(num);
 }
 }
 finally
 {
 disposable = (IDisposable)enumirator;
 disposable.Dispose();
 }

您可能认为对于集合的迭代,您需要实现IEnumerable和IEnumerable 接口。但是,这并不完全正确。要编译foreach,只需要实现GetEnumerator()方法,该方法将返回另一个具有Current属性和MoveNext()方法的对象。 这里我们使用duck typing - 一种众所周知的方法。

也就是说,如果有一个GetEnumerator()方法的对象,它返回一个带有MoveNext()方法和Current属性的对象,那么这就是枚举器。 否则,如果找不到必要的对象,并且找不到必要的方法,则将搜索IEnumerable和IEnumerable 接口。 因此,foreach实际上是一个通用循环,可以同时适用于数组和集合。我经常使用它。但是,foreach有一个缺点 - 它只允许读取元素,并且不允许更改它们。

posted on 2018-10-10 09:59  小欻欻1号  阅读(361)  评论(0编辑  收藏  举报