为什么数组可以使用 foreach 来遍历自己的元素呢?原因是数组可以按需提供一个叫做枚举器(enumerator)的对象。获取对象枚举器的方法是通过调用对象的 GetEnumerator 方法。实现GetEnumerator 方法的类型叫做可枚举类型。
foreach 结构设计用来和可枚举类型一起使用,比如数组,它就会执行以下行为:
-通过调用 GetEnumerator 方法获取对象的枚举器
-从枚举器中请求每一项并且把它作为迭代变量(iteration variable),代码可以读取该变量但不可以改变。
实现了 IEnumerator 接口的枚举器包含 3 个函数成员:Current、MoveNext 以及 Reset。
Current 时返回序列中当前位置项的属性(只读,返回Object 的引用,所以可以返回任何类型的对象)
MoveNext 是把枚举器位置前进到集合中下一项的方法(有效返回True,无效 False,初始位置在序列的第一项之前,所以在第一次使用 Current 之前要先调用 MoveNext )
Reset 是把位置重置为初始状态的方法
如此便可以得到与 foreach 方法相近的方法:
1 int[] arr1 = { 10, 11, 12, 13 }; // Create an array. 2 3 IEnumerator ie = arr1.GetEnumerator(); //获取并存储枚举器 4 5 while (ie.MoveNext()) 6 { 7 int item = (int)ie.Current; //获取当前项 8 Console.WriteLine($"Item value: { item }"); // Write it out. 9 }
可枚举类是指实现了 IEnumerable 接口的类。此接口只有一个成员——GetEnumerator 方法
public IEbumerator GetEnumerator{ ... }
自己实现可枚举类:
实现 IEnumerator 接口的枚举器(实现 Current、MoveNext 和 Reset 方法)
实现 IEnumerable 接口的枚举类(实现 GetEnumerator 方法)
实现对应的方法是重点
非泛型接口的实现是类型不安全的,它返回的是Object的引用,然后再转化为实际类型;泛型接口的枚举器是类型安全的,它返回实际类型的引用。
yield return 指定序列中要返回的下一项
yield return 指定在序列中没有其他项根据指定的返回类型,可以让迭代器产生枚举器或可枚举类型
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 MyClass mc = new MyClass(); 6 foreach (string shade in mc) 7 Console.Write($"{shade} "); 8 9 foreach (string shade in mc.BlackAndWhite()) 10 Console.Write($"{shade} "); 11 } 12 } 13 class MyClass 14 { 15 16 public IEnumerator<string> GetEnumerator() 17 { 18 IEnumerable<string> myEnumerable = BlackAndWhite(); 19 return myEnumerable.GetEnumerator(); 20 } 21 public IEnumerable<string> BlackAndWhite() 22 { 23 yield return "black"; 24 yield return "gray"; 25 yield return "white"; 26 } 27 }当我们实现返回枚举器的迭代器时,必须通过GetEnumertor方法来让类可枚举,它返回由迭代器返回的枚举器。 如果我们在类中实现迭代器返回可枚举类型,我们可以让类实现GetEnumerator来让类本身可被枚举,或者不实现GetEnumerator,让类不可枚举,只需要直接调用迭代器方法进行foreach枚举。
1 class Spectrum 2 { 3 string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" }; 4 5 public IEnumerable<string> UVtoIR() 6 { 7 for (int i = 0; i < colors.Length; i++) 8 yield return colors[i]; 9 } 10 11 public IEnumerable<string> IRtoUV() 12 { 13 for (int i = colors.Length - 1; i >= 0; i--) 14 yield return colors[i]; 15 } 16 } 17 18 class Program 19 { 20 static void Main() 21 { 22 Spectrum spectrum = new Spectrum(); 23 24 foreach (string color in spectrum.UVtoIR()) 25 Console.Write($"{ color } "); 26 Console.WriteLine(); 27 28 foreach (string color in spectrum.IRtoUV()) 29 Console.Write($"{ color } "); 30 Console.WriteLine(); 31 } 32 }
1 using System; 2 using System.Collections.Generic; 3 class Spectrum 4 { 5 bool _listFromUVtoIR; 6 string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" }; 7 8 public Spectrum(bool listFromUVtoIR) 9 { 10 _listFromUVtoIR = listFromUVtoIR; 11 } 12 13 public IEnumerator<string> GetEnumerator() 14 { 15 return _listFromUVtoIR 16 ? UVtoIR 17 : IRtoUV; 18 } 19 20 public IEnumerator<string> UVtoIR 21 { 22 get 23 { 24 for (int i = 0; i < colors.Length; i++) 25 yield return colors[i]; 26 } 27 } 28 29 public IEnumerator<string> IRtoUV 30 { 31 get 32 { 33 for (int i = colors.Length - 1; i >= 0; i--) 34 yield return colors[i]; 35 } 36 } 37 } 38 39 class Program 40 { 41 static void Main() 42 { 43 Spectrum startUV = new Spectrum(true); 44 Spectrum startIR = new Spectrum(false); 45 46 foreach (string color in startUV) 47 Console.Write($"{ color } "); 48 Console.WriteLine(); 49 50 foreach (string color in startIR) 51 Console.Write($"{ color } "); 52 Console.WriteLine(); 53 } 54 }