.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 }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· DeepSeek智能编程
· 精选4款基于.NET开源、功能强大的通讯调试工具
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?