今天写关于foreach的一些东西包括一下内容
foreach的介绍
foreach的简单用法
手工实现 IEnumerator 和 IEnumerable接口来兼容foreach
yield return关键字和迭代器
类似与for语句遍历集合(数组等数据结构)的语句。
看一下masn的介绍”foreach 语句对实现 System.Collections. IEnumerable 或 System.Collections.Generic. IEnumerable< T> 接口对象集合中的每个元素重复一组嵌入式语句”
另外在遍历过程中可以使用 break continue goto return throw这些方式在中途退出
很重要的一点是利用foreach只能实现只读,要修改集合中的内容就必须用for
更重要的是它的速度比用for语句快
关于效率问题可以参考《深入探讨C#foreach语句》的前半部分
文章地址:http://developer.51cto.com/art/200908/147347.htm
直接上代码
//一维数组的遍历 int[] a1 = new int[5] { 1, 2, 3, 4, 5 }; foreach (int element in a1) { Console.Write("{0} ", element); } Console.WriteLine(); //多维数组遍历 int[,] a2 = new int[3, 3] {{1,2,3}, {4,5,6}, {7,8,9}}; foreach (int element in a2) { Console.Write("{0} ", element); } Console.WriteLine(); //交错数组的遍历 int[][] a3 = new int[5][]; for (int i = 0; i < 5; i++) { a3[i] = new int[i + 1]; } foreach (int[] a in a3) { foreach (int e in a) { Console.Write("{0} ", e); } Console.WriteLine(); }
比较值得注意是交错数组部分
因为交错数组是数组的数组
所以代码中a3类型是int[][] a的类型是int[] e的类型是int
讲这个之前先讲一下foreach的原理(一下来自《C#入门经典》)
(1)调用collectionObject.GetEnumerator(),返回一个IEnumerator引用。这个方法可以通过IEnumerable接口的实现代码来获得。但这是可选的。
(2)调用返回的IEnumerator接口的MoveNext()方法。
(3)如果MoveNext()方法返回true,就使用IEnumerator接口的Current属性获取对象的一个引用,用于foreach循环。
(4)重复前面两步,直到MoveNext()方法返回false为止,此时循环停止。
那么如果你要为自己建立的集合提供foreach语句的支持,按照MSDN的说法应该“在 C# 中,集合类并非必须严格从 IEnumerable 和 IEnumerator 继承才能与 foreach 兼容;只要类有所需的 GetEnumerator 、MoveNext 、Reset 和 Current 成员,便可以与 foreach 一起使用。省略接口的好处为,使您可以将 Current 的返回类型定义得比 object 更明确,从而提供了类型安全。”
我们先来看一下这两个接口的定义
// 摘要: // 公开枚举数,该枚举数支持在非泛型集合上进行简单迭代。 public interface IEnumerable { // 摘要: // 返回一个循环访问集合的枚举数。 // // 返回结果: // 可用于循环访问集合的 System.Collections.IEnumerator 对象。 IEnumerator GetEnumerator(); } /// // 摘要: // 支持对非泛型集合的简单迭代。 public interface IEnumerator { // 摘要: // 获取集合中的当前元素。 // // 返回结果: // 集合中的当前元素。 // // 异常: // System.InvalidOperationException: // 枚举数定位在该集合的第一个元素之前或最后一个元素之后。- 或 - 在创建了枚举数后集合被修改了。 object Current { get; } // 摘要: // 将枚举数推进到集合的下一个元素。 // // 返回结果: // 如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。 // // 异常: // System.InvalidOperationException: // 在创建了枚举数后集合被修改了。 bool MoveNext(); // // 摘要: // 将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。 // // 异常: // System.InvalidOperationException: // 在创建了枚举数后集合被修改了。 void Reset(); }
只要我们的集合类能实现这两个接口就可以使用foreach对集合进行遍历
具体的代码如下
public class Element { public int n1; public int n2; public Element(int n1, int n2) { this.n1 = n1; this.n2 = n2; } } public class List : IEnumerator , IEnumerable { private int index = -1; private Element[] element; public List(Element[] element) { this.element = new Element[element.Length]; for (int i = 0; i < element.Length; i++) { this.element[i] = element[i]; } } public IEnumerator GetEnumerator() { return (IEnumerator)this; } public bool MoveNext() { index++; return (index < element.Length); } public object Current { get { return element[index]; } } public void Reset() { index = -1; } } class Program { static void Main(string[] args) { //集合的遍历 Element[] eArray = new Element[3] { new Element(1,2), new Element(3,4), new Element(5,6), }; List eList = new List(eArray); foreach (Element e in eList) { Console.WriteLine("{0},{1}", e.n1, e.n2); } Console.ReadKey(); } }
如果还有不清楚可以对比一下MSDN中的实例
http://msdn.microsoft.com/zh-cn/library/9yb8xew9%28v=VS.80%29.aspx
如果为了让集合支持foreach语句要多出那么一大块的代码,确是有点麻烦。
所以自然有一种简单的办法出现了,使用迭代器。
这东西可以再后台帮你生成很多代码
在为类或结构创建迭代器时,不必实现整个 IEnumerator 接口。
当编译器检测到迭代器时,它将自动生成 IEnumerator 接口的 Current 、MoveNext 和 Dispose 方法。
然后利用yield return语句依次返回每个元素,选择要在foreach循环中使用的值。
到达yield return语句时,会保存当前迭代的位置,下次调用迭代器时将从此位置开始执行。
那如何创建一个迭代器块呢?
也很简单yield 关键字就是向编译器指示它所在的方法是迭代器块。
不知道这样写有没有表达清楚,还是代码来的直接点
public class Primes { private long min; private long max; public Primes() : this(2, 100) { } public Primes(long minimum, long maximum) { if (min < 2) { minimum = 2; } else { min = minimum; } max = maximum; } public IEnumerator GetEnumerator() { for (long possiblePrime = min; possiblePrime <= max; possiblePrime++) { bool isPrime = true; for (long possibleFactor = 2; possibleFactor <= (long)Math.Floor(Math.Sqrt(possiblePrime)); possibleFactor++) { long remainderAfterDivision = possiblePrime % possibleFactor; if (remainderAfterDivision == 0) { isPrime = false; break; } } if (isPrime) { yield return possiblePrime; } } } } class Program { static void Main(string[] args) { Primes test2to1000 = new Primes(2, 1000); foreach (long i in test2to1000) { Console.Write("{0} ",i); } Console.ReadKey(); } }
实现了一个打印素数的功能。
值得一提的是,那些数是一个一个出来的。可以再输出语句后再加输出点东西就看出来了
也就是迭代器一次只返回一个结果,调用yield的时候会中断代码的执行,等foreach开始新的循环代码才回复执行。
同时也给一段比较短的代码作为对照
public class DaysOfTheWeek : System.Collections.IEnumerable { string[] days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" }; public System.Collections.IEnumerator GetEnumerator() { for (int i = 0; i < days.Length; i++) { yield return days[i]; } } } class TestDaysOfTheWeek { static void Main() { DaysOfTheWeek week = new DaysOfTheWeek(); foreach (string day in week) { System.Console.Write(day + " "); } } } // Output: Sun Mon Tue Wed Thr Fri Sat
如果有心的话应该可以猜到 yield return 返回的是一个object类型的值。所以不用再自己指定了。
。