.Net学习难点讨论系列11 - foreach与迭代器,枚举数与可枚举值
本文主要整理于ASP.NET2.0开发指南部分内容、C#图解教程
要使一个自定义的集合类可以与foreach一起工作,我们需要实现IEnumerable接口(GetEnumerator方法)或实现IEnumerator<T>接口。
在.NET Framework枚举迭代相关类设计中IEnumerator是基础,其包含三个成员其中一个Current属性,两个函数Movenext与Reset。这些是一个集合对象可枚举的基础。而IEnumerable接口只包含一个GetEnumerator方法,作用就是返回一个IEnumerator对象。
.NET2.0新增的泛型接口IEnumerator<T>实现了IEnumerator与IDisposal接口,该接口自身包含一个Current属性,该属性返回一个T类型的对象。我们实现此接口时,需要实现其自身Current属性及父接口IEnumerator与IDisposal的方法。这其中特别需要注意的是,我们需要显式实现IEnumerator中的Current属性:
1 object System.Collections.IEnumerator.Current 2 { 3 get { throw new NotImplementedException(); } 4 }
另一个泛型接口IEnumerable<T>继承自IEnumerable接口,其自身包含一个GetEnumerator方法返回泛型版本的IEnumerator<T>对象。类似于实现IEnumerator<T>,在实现IEnumerable<T>时,除了实现返回IEnumerator<T>的GetEnumerator方法,还要显示实现IEnumerable中的GetEnumerator方法:
1 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 2 { 3 throw new NotImplementedException(); 4 }
了解了这些后就很容易解释迭代器(yield)了,迭代器提供给我们最简单的创建一个IEnumerator<T>对象或IEnumerable<T>的途径,见下面的代码段:
1 //返回泛型枚举数 2 public IEnumerator<string> SampleMethod1() 3 { 4 yield return "tree"; 5 yield return "flower"; 6 } 7 8 //返回泛型可枚举对象 9 public IEnumerable<string> SampleMethod2() 10 { 11 yield return "tree"; 12 yield return "flower"; 13 }
综合以上,下面看一个完整的例子:
1 public class Persons : IEnumerable 2 { 3 public string[] m_Names; 4 public Persons(params string[] Names) 5 { 6 m_Names = new string[Names.Length]; 7 Names.CopyTo(m_Names, 0); 8 } 9 10 //实现索引器 11 public string this[int index] 12 { 13 get 14 { 15 return m_Names[index]; 16 } 17 set 18 { 19 m_Names[index] = value; 20 } 21 } 22 23 //实现IEnumerable成员 24 //需返回一个IEnumerator类型的对象 25 #region IEnumerable 成员 26 27 IEnumerator IEnumerable.GetEnumerator() 28 { 29 return new PersonsEnumerator(this); 30 } 31 32 #endregion 33 } 34 35 public class PersonsEnumerator : IEnumerator 36 { 37 int index = -1; 38 Persons P; 39 public PersonsEnumerator(Persons P) 40 { 41 this.P = P; 42 } 43 44 #region IEnumerator 成员 45 46 object IEnumerator.Current 47 { 48 get 49 { 50 return P.m_Names[index]; 51 } 52 } 53 54 bool IEnumerator.MoveNext() 55 { 56 int tempIndex = ++index; 57 if (tempIndex >= P.m_Names.Length) 58 { 59 return false; 60 } 61 else 62 { 63 return true; 64 } 65 } 66 67 void IEnumerator.Reset() 68 { 69 index = -1; 70 } 71 #endregion 72 }
使用(非泛型)迭代器的方式:
1 static void Main() 2 { 3 Persons arrPersons = new Persons("Michel", "Rebecca", "Polaris"); 4 foreach (string s in arrPersons) 5 { 6 Console.WriteLine(s); 7 } 8 }
在C# 2.0中我们可以通过新增的关键字 – yeild,来方便的实现一个迭代器。而且.NET2.0中新增的泛型可以使迭代器强类型化。
一步步进行:首先我们将上述程序改写为用yield关键字实现(仍然是非泛型)。
1 public class Persons : IEnumerable 2 { 3 public string[] m_Names; 4 public Persons(params string[] Names) 5 { 6 m_Names = new string[Names.Length]; 7 Names.CopyTo(m_Names, 0); 8 } 9 10 #region IEnumerable 成员 11 12 IEnumerator IEnumerable.GetEnumerator() 13 { 14 for (int i = 0; i < m_Names.Length; i++) 15 { 16 yield return m_Names[i]; 17 } 18 } 19 20 #endregion 21 }
接下来,我们将此方法改写为泛型的实现:
1 public class Persons<T> : IEnumerable<T> 2 { 3 public T[] m_Names; 4 public Persons(params T[] Names) 5 { 6 m_Names = new T[Names.Length]; 7 Names.CopyTo(m_Names, 0); 8 } 9 10 #region IEnumerable<T> 成员 11 12 IEnumerator<T> IEnumerable<T>.GetEnumerator() 13 { 14 for (int i = 0; i < m_Names.Length; i++) 15 { 16 yield return m_Names[i]; 17 } 18 } 19 20 #endregion 21 }
泛型版迭代器的使用:
1 static void Main() 2 { 3 Persons<string> arrPersons = new Persons<string>("Michel", "Rebecca", "Polaris"); 4 foreach (string s in arrPersons) 5 { 6 Console.WriteLine(s); 7 } 8 }
上文基本列出了怎样使用yield实现一个迭代器。
下面来说一下yield的一些使用方式:
1. 终止yield迭代的方式
1 IEnumerator IEnumerable.GetEnumerator() 2 { 3 for (int i = 0; i < m_Names.Length; i++) 4 { 5 yield return m_Names[i]; 6 if (i >= 1) 7 { 8 yield break; 9 } 10 } 11 }
2. 逆序迭代内容
1 IEnumerator IEnumerable.GetEnumerator() 2 { 3 for (int i = (m_Names.Length - 1); i >= 0; --i) 4 { 5 yield return m_Names[i]; 6 } 7 }
3. 迭代器的另一种实现,返回实现IEnumerable<T>接口的对象,不实现GetEnumerator()方法。
1 public class Persons<T> 2 { 3 private T[] m_Names; 4 public Persons(params T[] Names) 5 { 6 m_Names = new T[Names.Length]; 7 Names.CopyTo(m_Names, 0); 8 } 9 10 public IEnumerable<T> GetPersons() 11 { 12 for (int i = 0; i < m_Names.Length; i++) 13 { 14 yield return m_Names[i]; 15 } 16 } 17 } 18 19 class Program 20 { 21 static void Main() 22 { 23 Persons<string> arrPersons = new Persons<string>("Michel", "Rebecca", "Polaris"); 24 foreach (string s in arrPersons.GetPersons()) 25 { 26 Console.WriteLine(s); 27 } 28 } 29 }