IEnumerable / IEnumerator

首先,IEnumerable / IEnumerator 接口定义如下:

public interface IEnumerable  /// 可枚举接口
{
IEnumerator GetEnumerator();
}
public interface IEnumerator /// 枚举器接口
{
object Current { get; }
bool MoveNext();
void Reset();
}

注:Current 没有 set 方法,在 foreach 中不能修改元素 var item 的值。

  • IEnumerable:声明式的接口,声明实现了该接口的类是可枚举类型;
  • IEnumerator:实现式的接口,IEnumerator 对象说明如何实现一个枚举器;

通过继承 IEnumerable / IEnumerator 接口实现自定义类使用 foreach 语句来遍历自身元素。逻辑关系图:

People <-> MyClass 实现 IEnumerable 接口的 GetEnumerator()方法
EnumeratorPeople <-> MyEnumerator 实现 IEnumerator 接口
  • 定义Person类
public class Person
{
 private string name;
 private int age;  public Person(string _name, int _age){
  this.name = _name, this.age = _age;
 }
 public override string ToString(){
  return string.Format("Name:{0},Age:{1}.", name, age);
 }
}

数组定义见主函数,以下2种遍历数组的方法等同,因为所有数组的基类都是 System.Array ,System.Array 类实现了 IEnumerable 接口,可以直接通过 GetEnumerator() 方法返回枚举数对象,枚举数对象可以依次返回请求的数组的元素。

// 利用 foreach 遍历数组
foreach (var person in persons)
Console.WriteLine(person.ToString());
// 利用 IEnumerable ~ IEnumerator 遍历数组
IEnumerator it = persons.GetEnumerator();
while (it.MoveNext()){
Person obj = (Person)(it.Current); // 需强制类型转换
Console.WriteLine(obj.ToString());
}
  • 定义People类 (MyClass)
public class People
{
private Person[] persons;
public People(Person[] _persons){
persons = new Person[_persons.Length];
for (int i = 0; i < _persons.Length; ++i){
persons[i] = _persons[i];
}
}
} 

注意,People 类的 persons 数组是 private 变量,在主测函数中是无法遍历访问的。
方法 1:将 private 更改为 public:

foreach (var person in people.persons)
Console.WriteLine(person.ToString());

方法 2:People 类继承 IEnumerable 接口并实现 GetEnumerator() 方法,有 2 种方法:
[-1-]. 利用数组默认实现了 IEnumerable 和 IEnumerator 接口的事实,重新定义 People 类:

public class People : IEnumerable
{
... ...
public IEnumerator GetEnumerator(){
return persons.GetEnumerator(); // 方法 1
}
}

[-2-]. 自定义枚举数类 (EnumeratorPeople 类如下),继承并实现 IEnumerator 接口,重新定义 People 类:

public class People : IEnumerable
{
... ...
public IEnumerator GetEnumerator(){
return new EnumeratorPeople(persons); // 方法 2
}
}

此时,在方法2中自定义类可以使用如下 foreach 语句来遍历自身元素:

foreach (var person in people)
Console.WriteLine(person.ToString());
  • 定义EnumeratorPeople类 (MyEnumerator)
public class EnumeratorPeople : IEnumerator
{
private int position = -1;
private Person[] persons;
public EnumeratorPeople(Person[] _persons){
persons = new Person[_persons.Length];
for (int i = 0; i < _persons.Length; ++i)
persons[i] = _persons[i];
} public object Current{
get{ return persons[position]; }
}
public bool MoveNext(){
++position;
return (position < persons.Length);
}
public void Reset(){
position = -1;
}
}

主函数测试代码

class Program{
static void Main(string[] args){
Person[] persons = {
new Person("abc",25), new Person("xyz",22),
new Person("qwer",12), new Person("pm",20) };
People people = new People(persons);
}
}

总结

一个类型是否支持foreach遍历,本质上是实现 IEnumerator 接口,2 种方法:
(1)自定义类只要继承 IEnumerable 接口并实现无参 GetEnumerator() 方法即可,最简单;
(2)在(1)基础上,定义 MyEnumerator 类继承并实现 IEnumerator 接口;

扩展

foreach 语句隐式调用集合的无参 GetEnumerator() 方法。其实,不论集合是否有实现 IEnumerable 接口,只要必须提供无参 GetEnumerator() 方法并返回包含 Current 属性和 MoveNext() 方法的 IEnumerator 对象即可,然后编译器会自动去绑定,无需依赖 IEnumerable 和 IEnumerator 接口,从而实现 foreach 对自定义集合类的遍历。

   public class People
  {
    public class EnumeratorPeople
    {
private int position = -;
private Person[] enumPersons;
public EnumeratorPeople(Person[] _persons){
enumPersons = new Person[_persons.Length];
for (int i = ; i < _persons.Length; ++i){
enumPersons[i] = _persons[i];
}
} public object Current{
get { return enumPersons[position]; }
}
public bool MoveNext(){
++position;
return ( position < enumPersons.Length );
}
public void Reset(){
position = -;
}
} private Person[] persons;
public People(Person[] _persons){
persons = new Person[_persons.Length];
for (int i = ; i < _persons.Length; ++i){
persons[i] = _persons[i];
}
}
public EnumeratorPeople GetEnumerator(){
return new EnumeratorPeople(persons);
}
  }

此处,枚举数类声明为嵌套类,或者集成为一个类,也可以分成单独的 2 个类均可。

延伸问题

  • for 与 foreach

for 先取全部再遍历,foreach 边遍历边取值;

  • Linq to Object 中返回 IEnumerable 类型?

IEnumerable 是延迟加载的。

参考

·传统遍历与迭代器
·IEnumerable和IEnumerator 详解
·自定义类实现foreach深入理解 foreach


IEnumerable<T> / IEnumerator<T>

优缺点对比
·  非泛型:非类型安全,返回object类型的引用、需要再转化为实际类型 (值类型需要装箱和拆箱);
·  泛型:类型安全,直接返回实际类型的引用;
首先,IEnumerable<T> / IEnumerator<T> 接口定义如下:

public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator, IDisposable
{
T Current { get; }
}

其中,接口 IDisposable 定义为:

public interface IDisposable{
void Dispose();
}

逻辑关系图:
    

   People <-> MyClass 实现 IEnumerable<T> 接口的 泛型GetEnumerator()方法
GenEnumPeople <-> MyGenEnumerator 实现 IEnumerator<T> 接口

:显式实现非泛型版本,在类中实现泛型版本!如下,类 MyGenEnumerator 实现了 IEnumerator<T>,类MyClass 实现了 IEnumerable<T> 接口。

 public class MyClass : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator() { } // IEnumerable<T> 版本
IEnumerator IEnumerable.GetEnumerator() { } // IEnumerable 版本
} public class MyGenEnumerator : IEnumerator<T>
{
public T Current { get; } // IEnumerator<T> 版本
public bool MoveNext() { }
public void Reset() { }
object IEnumerator.Current { get; } // IEnumerator 版本
public void Dispose() { }
}

·  定义 People 类 (MyClass)  

    public class People : IEnumerable<Person>
{
private Person[] persons;
public People(Person[] _persons){
persons = new Person[_persons.Length];
for (int i = ; i < _persons.Length; ++i)
persons[i] = _persons[i];
} public IEnumerator<Person> GetEnumerator(){
return new GenericEnumeratorPeople(persons);
}
IEnumerator IEnumerable.GetEnumerator(){ // 显式实现
return this.GetEnumerator();
}
}

·  定义 GenEnumPeople 类 (MyGenEnumerator)

    public class GenEnumPeople : IEnumerator<Person>
{
private int position = -;
private Person[] persons;
public GenericEnumeratorPeople(Person[] _persons){
persons = new Person[_persons.Length];
for (int i = ; i < _persons.Length; ++i)
persons[i] = _persons[i];
} public Person Current{
get { return persons[position]; }
}
object IEnumerator.Current{ // 显式实现
get { return this.Current; }
}
public bool MoveNext(){
++position;
return (position < persons.Length);
}
public void Reset() { position = -; }
public void Dispose() { Console.WriteLine("void Dispose()"); }
}

其中,IDisposable 接口的学习参见 由 IDisposable 到 GC

扩展 泛型委托应用


迭代器

  C#2.0 利用迭代器可以简单实现 GetEnumerator() 函数。迭代器是用于返回相同类型的值的有序集合的一段代码。利用 yield 关键字,实现控制权的传递和循环变量的暂存,使类或结构支持 foreach 迭代,而不必显式实现 IEnumerable 或 IEnumerator 接口,由 JIT 编译器辅助编译成实现了 IEnumerable 或 IEnumerator 接口的对象。yield return 提供了迭代器一个重要功能,即取到一个数据后马上返回该数据,不需要全部数据加载完毕,有效提高遍历效率(延迟加载)。
 - yield 关键字用于指定返回的值,yield return 语句依次返回每个元素,yield break 语句终止迭代;
 - 到达 yield return 语句时,保存当前位置,下次调用迭代器时直接从当前位置继续执行;
 - 迭代器可以用作方法、运算符或get访问器的主体实现,yield 语句不能出现在匿名方法中;
 - 迭代器返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>;
 
逻辑关系图: 
    

· · 返回 IEnumerator 类型

 返回此类型的迭代器通常为默认迭代器。
 ·  People 类

  public class People
{
   private Person[] persons;
   public People(Person[] _persons){
      persons = new Person[_persons.Length];
      for (int i = ; i < _persons.Length; ++i)
         persons[i] = _persons[i];
   }    public IEnumerator GetEnumerator(){
      return IterMethod1; // [1]
      return IterMethod2(); // [2]
   }
   public IEnumerator IterMethod1 // [1].属性返回 IEnumerator
   {
      get {
         for (int i = ; i < persons.Length; ++i)
            yield return persons[i];
      }
   }
   public IEnumerator IterMethod2() // [2].函数返回 IEnumerator (推荐)
   {
      for (int i = ; i < persons.Length; ++i)
         yield return persons[i];
   }
}

·主函数测试代码

  People people = new People(persons);
foreach (var person in people)
   Console.WriteLine(person.ToString());

· ·返回 IEnumerable 类型

 返回此类型的迭代器通常用于实现自定义迭代器。
 ·  People 类

  public class People
{
   private Person[] persons;
   public People(Person[] _persons){
      persons = new Person[_persons.Length];
      for (int i = ; i < _persons.Length; ++i)
         persons[i] = _persons[i];
   }
   
   public IEnumerator GetEnumerator(){
      return IterMethod1.GetEnumerator(); // [1]
      return IterMethod2().GetEnumerator(); // [2]
   }
   public IEnumerable IterMethod1 // [1].自定义迭代器 1
   {
      get{
         for (int i = ; i < persons.Length; ++i)
            yield return persons[i];
      }
   }
   public IEnumerable IterMethod2() // [2].自定义迭代器 2 (推荐)
   {
      for (int i = ; i < persons.Length; ++i)
         yield return persons[i];
   }
}

·主函数测试代码

  People people = new People(persons);
foreach (var person in people) // 默认
   Console.WriteLine(person.ToString());
foreach (var person in people.IterMethod1) // [1]
   Console.WriteLine(person.ToString());
foreach (var person in people.IterMethod2()) // [2]
   Console.WriteLine(person.ToString());

 对于返回泛型IEnumerator<T>、IEnumerable<T>的迭代器,同理。

参考

·foreach 与 yield

C# ~ 从 IEnumerable / IEnumerator 到 IEnumerable<T> / IEnumerator<T> 到 yield的更多相关文章

  1. 在自己的对象里实现IEnumerator和IEnumerable

    平时工作中我们经常用foreach来迭代一个集合.比如 foreach (Student student in myClass) { Console.WriteLine(student); } 基本所 ...

  2. C#深度学习の枚举类型(IEnumerator,IEnumerable)

    一.关于枚举的含义 .Net提供了可枚举类型的接口IEnumerable和枚举器接口IEnumerator,程序集System.Collections 另: IQueryable 继承自IEnumer ...

  3. IEnumerator和IEnumerable详解

    IEnumerator和IEnumerable 从名字常来看,IEnumerator是枚举器的意思,IEnumerable是可枚举的意思. 了解了两个接口代表的含义后,接着看源码: IEnumerat ...

  4. 2021年了,`IEnumerator`、`IEnumerable`还傻傻分不清楚?

    IEnumerator.IEnumerable这两个接口单词相近.含义相关,傻傻分不清楚. 入行多年,一直没有系统性梳理这对李逵李鬼. 最近本人在怼着why神的<其实吧,LRU也就那么回事> ...

  5. IEnumerable< T >和IEnumerable区别 |枚举接口

    为什么我们在继承IEnumerable< T >接口的时候也要实现IEnumerable接口. 新的代码里面都用IEnumerable< T >,因为泛型的类型是安全的.我们可 ...

  6. 实现了IEnumerable接口的GetEnumerator 即可使用 Foreach遍历,返回一个IEnumerator对象

    #region 程序集 mscorlib.dll, v4.0.0.0 // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framewor ...

  7. 理解IEnumerator+IEnumerable这种接口思想

    前言 本文不想过多篇幅来介绍IEnumerator和IEnumerable这两个接口的具体说明,只是把它作一个例子作引言而已,本文将根据自己的理解来描述微软为何要这样设计这种关联风格的接口.这种风格的 ...

  8. IEnumerable和IEnumerator

    概述 IEnumerable和IEnumerator接口存在的意义:用来实现迭代的功能! public interface IEnumerable { IEnumerator GetEnumerato ...

  9. 关于迭代器中IEnumerable与IEnumerator的区别

    首先是IEnumerable与IEnumerator的定义: 1.IEnumerable接口允许使用foreach循环,包含GetEnumerator()方法,可以迭代集合中的项. 2.IEnumer ...

随机推荐

  1. the bundle at bundle path is not signed using an apple submission certificate

    在app上架的时候,出现这个错误,也许只是你的Apple Worldwide Developer Relations Certification Authority Intermediate Cert ...

  2. 使用ViwePager显示图片时如何防止内存泄露。

    内存泄露的检测. 1. 在Android Studio中运行你的应用,然后切换到输出窗口的Android tab. 2. 尽情的玩耍你的应用,最好各个功能都用到,如果是Viewpager,则多滑动一些 ...

  3. [译+改]最长回文子串(Longest Palindromic Substring) Part II

    [译+改]最长回文子串(Longest Palindromic Substring) Part II 原文链接在http://leetcode.com/2011/11/longest-palindro ...

  4. NanoProfiler - 适合生产环境的性能监控类库 之 实践ELK篇

    上期回顾 上一期:NanoProfiler - 适合生产环境的性能监控类库 之 大数据篇 上次介绍了NanoProfiler的大数据分析理念,一晃已经时隔一年多了,真是罪过! 有朋友问到何时开源的问题 ...

  5. 大叔也说Xamarin~Android篇~日志的记录

    回到目录 无论哪个平台,开始哪种应用程序,日志总是少不了的,大家在Lind.DDD里也可以看到大叔的日志组件,而在xamarin进行移动开发时,为了更好的调试,记录运行的情况,日志也是必须的,这讲主要 ...

  6. EF架构~对AutoMapper实体映射的扩展

    回到目录 AutoMapper在之前我曾经介绍过,今天主要是把它作一下扩展,因为它的调用太麻烦了,呵呵,扩展之后,用着还可以,感觉.net3.5之后,有了扩展方法这个东西,在程序开发速度及表现力上都有 ...

  7. Beats数据采集---Packetbeat\Filebeat\Topbeat\WinlogBeat使用指南

    Beats是elastic公司的一款轻量级数据采集产品,它包含了几个子产品: packetbeat(用于监控网络流量). filebeat(用于监听日志数据,可以替代logstash-input-fi ...

  8. C# WebBrowser 获得选中部分的html源码

    Winform程序 2.0的. 需要引用Microsoft.mshtml. private void Form1_Load(object sender, EventArgs e) { webBrows ...

  9. 自引用指针this

    C++为成员函数提供了一个名字为this的指针,这个指针称为自引用指针,每当创建一个对象时,系统就把this指针初始化为指向该对象,即this指针的值是当前调用成员函数的对象的起始地址.每当调用一个成 ...

  10. sys.dm_db_wait_stats

    sys.dm_db_wait_stats 返回在操作期间执行的线程所遇到的所有等待的相关信息. 可以使用此聚合视图来诊断 Azure SQL Database 以及特定查询和批处理的性能问题. 执行查 ...