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. Android多线程分析之一:使用Thread异步下载图像

    Android多线程分析之一:使用Thread异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处   打算整理一下对 Android F ...

  2. Mysql跨表更新 多表update sql语句总结

    Mysql跨表更新一直是大家所关心的话题,本文介绍mysql多表 update在实践中几种不同的写法 假定我们有两张表,一张表为Product表存放产品信息,其中有产品价格列Price:另外一张表是P ...

  3. SVN 使用

    我是一个前端,svn 的服务器配置也是后端弄好的,到底怎么弄的不清楚. 最开始是想和xcode关联起来,每次提交代码也方便,但是在Xcode里的偏好设置Accounts 模块 添加了SVN 服务端地址 ...

  4. PMO到底什么样?(2)

    接上一篇,继续聊一聊PMO到底什么样. 交付功能,8大典型责任 1监控.评定和报告 项目办理单位从交付的视点必定要有监控评定.每个项目在要害的期间上它的进展是不是跟按期的相同:是不是有要害的专家在要害 ...

  5. 带你走近AngularJS - 体验指令实例

    带你走近AngularJS系列: 带你走近AngularJS - 基本功能介绍 带你走近AngularJS - 体验指令实例 带你走近AngularJS - 创建自定义指令 ------------- ...

  6. [Java面试二]Java基础知识精华部分.

    一:java概述(快速浏览): 1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒.PDA等的微处理器: 1994年将Oak语言更名 ...

  7. fir.im Weekly - 从 iOS 10 SDK 新特性说起

    从 iOS 7 翻天覆地的全新设计,iOS 8 中 Size Classes 的出现,应用扩展,以及 Cloud Kit 的加入,iOS 9 的分屏多任务特性,今年的 WWDC iOS 10 SDK ...

  8. iOS-应用打包发布常见问题

    这个月公司安排我一个人做iOS客户端开发,由于急着用,我先发布一个版本,由于第一次发布iOS应用,期间出了不少问题,记录于此. 1.使用Application Loader 发布时报错:Communi ...

  9. Python之函数与变量

    本节内容 函数介绍及其作用 函数的定义与调用 函数的参数说明 全局变量与局部变量 值传递和引用传递 一.函数的介绍及其作用 编程语言中的函数与数学中的函数是有区别的:数学中的函数有参数(输入),就会有 ...

  10. MongoDB修改器的使用1

    为什么要使用修改器?     通常我们只会修改文档的一部分,这时候更新整个文档就显得很麻烦,通常是通过原子性的更新修改器来完成. 1."$set"修改器    "$set ...