IEnumerable接口是非常的简单,只包含一个抽象的方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象。IEnumerator对象有什么呢?它是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进行集合的循环遍历是不可能的事情了。那么让我们看看IEnumerator接口有定义了什么东西。看下图我们知道IEnumerator接口定义了一个Current属性,MoveNext和Reset两个方法,这是多么的简约。既然IEnumerator对象时一个访问器,那至少应该有一个Current属性,来获取当前集合中的项吧。

MoveNext方法只是将游标的内部位置向前移动(就是移到一下个元素而已),要想进行循环遍历,不向前移动一下怎么行呢?

详细讲解:

说到IEnumerable总是会和IEnumerator、foreach联系在一起。

C# 支持关键字foreach,允许我们遍历任何数组类型的内容:

//遍历数组的项

int[] myArrayOfInts = {10,20,30,40};

foreach(int i in my myArrayOfInts)

{

Console.WirteLine(i);

}

虽然看上去只有数组才可以使用这个结构,其实任何支持GetEnumerator()方法的类型都可以通过foreach结构进行运算。

  1. public class Garage
  2. {
  3. Car[] carArray = new Car[4];  //在Garage中定义一个Car类型的数组carArray,其实carArray在这里的本质是一个数组字段
  4. //启动时填充一些Car对象
  5. public Garage()
  6. {
  7. //为数组字段赋值
  8. carArray[0] = new Car("Rusty", 30);
  9. carArray[1] = new Car("Clunker", 50);
  10. carArray[2] = new Car("Zippy", 30);
  11. carArray[3] = new Car("Fred", 45);
  12. }
  13. }

理想情况下,与数据值数组一样,使用foreach构造迭代Garage对象中的每一个子项比较方便:

  1. //这看起来好像是可行的
  2. lass Program
  3. {
  4. static void Main(string[] args)
  5. {
  6. Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");
  7. Garage carLot = new Garage();
  8. //交出集合中的每一Car对象吗
  9. foreach (Car c in carLot)
  10. {
  11. Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);
  12. }
  13. Console.ReadLine();
  14. }
  15. }

让人沮丧的是,编译器通知我们Garage类没有实现名为GetEnumerator()的方法(显然用foreach遍历Garage对象是不可能的事情,因为Garage类没有实现GetEnumerator()方法,Garage对象就不可能返回一个IEnumerator对象,没有IEnumerator对象,就不可能调用方法MoveNext(),调用不了MoveNext,就不可能循环的了)。这个方法是有隐藏在System.collections命名空间中的IEnumerable接口定义的。(特别注意,其实我们循环遍历的都是对象而不是类,只是这个对象是一个集合对象

支持这种行为的类或结构实际上是宣告它们向调用者公开所包含的子项:

//这个接口告知调方对象的子项可以枚举

public interface IEnumerable

{

IEnumerator GetEnumerator();

}

可以看到,GetEnumerator方法返回对另一个接口System.Collections.IEnumerator的引用。这个接口提供了基础设施,调用方可以用来移动IEnumerable兼容容器包含的内部对象。

//这个接口允许调用方获取一个容器的子项

public interface IEnumerator

{

bool MoveNext();             //将游标的内部位置向前移动

object Current{get;}       //获取当前的项(只读属性)

void Reset();                 //将游标重置到第一个成员前面

}

所以,要想Garage类也可以使用foreach遍历其中的项,那我们就要修改Garage类型使之支持这些接口,可以手工实现每一个方法,不过这得花费不少功夫。虽然自己开发GetEnumerator()、MoveNext()、Current和Reset()也没有问题,但有一个更简单的办法。因为System.Array类型和其他许多类型(如List)已经实现了IEnumerable和IEnumerator接口,你可以简单委托请求到System.Array,如下所示:

  1. namespace MyCarIEnumerator
  2. {
  3. public class Garage:IEnumerable
  4. {
  5. Car[] carArray = new Car[4];
  6. //启动时填充一些Car对象
  7. public Garage()
  8. {
  9. carArray[0] = new Car("Rusty", 30);
  10. carArray[1] = new Car("Clunker", 50);
  11. carArray[2] = new Car("Zippy", 30);
  12. carArray[3] = new Car("Fred", 45);
  13. }
  14. public IEnumerator GetEnumerator()
  15. {
  16. return this.carArray.GetEnumerator();
  17. }
  18. }
  19. }
  20. //修改Garage类型之后,就可以在C#foreach结构中安全使用该类型了。
  1. //除此之外,GetEnumerator()被定义为公开的,对象用户可以与IEnumerator类型交互:
  2. namespace MyCarIEnumerator
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");
  9. Garage carLot = new Garage();
  10. //交出集合中的每一Car对象吗
  11. foreach (Car c in carLot)  //之所以遍历carLot,是因为carLot.GetEnumerator()返回的项时Car类型,这个十分重要
  12. {
  13. Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);
  14. }
  15. Console.WriteLine("GetEnumerator被定义为公开的,对象用户可以与IEnumerator类型交互,下面的结果与上面是一致的");
  16. //手动与IEnumerator协作
  17. IEnumerator i = carLot.GetEnumerator();
  18. while (i.MoveNext())
  19. {
  20. Car myCar = (Car)i.Current;
  21. Console.WriteLine("{0} is going {1} MPH", myCar.CarName, myCar.CurrentSpeed);
  22. }
  23. Console.ReadLine();
  24. }
  25. }
  26. }

下面我们来看看手工实现IEnumberable接口和IEnumerator接口中的方法:

  1. namespace ForeachTestCase
  2. {
  3. //继承IEnumerable接口,其实也可以不继承这个接口,只要类里面含有返回IEnumberator引用的GetEnumerator()方法即可
  4.   class ForeachTest:IEnumerable
  5.    {
  6. private string[] elements;  //装载字符串的数组
  7. private int ctr = 0;  //数组的下标计数器
  8. /// <summary>
  9. /// 初始化的字符串
  10. /// </summary>
  11. /// <param name="initialStrings"></param>
  12. ForeachTest(params string[] initialStrings)
  13. {
  14. //为字符串分配内存空间
  15. elements = new String[8];
  16. //复制传递给构造方法的字符串
  17. foreach (string s in initialStrings)
  18. {
  19. elements[ctr++] = s;
  20. }
  21. }
  22. /// <summary>
  23. ///  构造函数
  24. /// </summary>
  25. /// <param name="source">初始化的字符串</param>
  26. /// <param name="delimiters">分隔符,可以是一个或多个字符分隔</param>
  27. ForeachTest(string initialStrings, char[] delimiters)
  28. {
  29. elements = initialStrings.Split(delimiters);
  30. }
  31. //实现接口中得方法
  32. public IEnumerator GetEnumerator()
  33. {
  34. return  new ForeachTestEnumerator(this);
  35. }
  36. private class ForeachTestEnumerator : IEnumerator
  37. {
  38. private int position = -1;
  39. private ForeachTest t;
  40. public ForeachTestEnumerator(ForeachTest t)
  41. {
  42. this.t = t;
  43. }
  44. public object Current
  45. {
  46. get
  47. {
  48. return t.elements[position];
  49. }
  50. }
  51. public bool MoveNext()
  52. {
  53. if (position < t.elements.Length - 1)
  54. {
  55. position++;
  56. return true;
  57. }
  58. else
  59. {
  60. return false;
  61. }
  62. }
  63. public void Reset()
  64. {
  65. position = -1;
  66. }
  67. }
  68. static void Main(string[] args)
  69. {
  70. ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence.");
  71. foreach (string item in f)
  72. {
  73. System.Console.WriteLine(item);
  74. }
  75. Console.ReadKey();
  76. }
  77. }
  78. }

IEnumerable<T>接口

实现了IEnmerable<T>接口的集合,是强类型的。它为子对象的迭代提供类型更加安全的方式。

  1. public  class ListBoxTest:IEnumerable<String>
  2. {
  3. private string[] strings;
  4. private int ctr = 0;
  5. #region IEnumerable<string> 成员
  6. //可枚举的类可以返回枚举
  7. public IEnumerator<string> GetEnumerator()
  8. {
  9. foreach (string s in strings)
  10. {
  11. yield return s;
  12. }
  13. }
  14. #endregion
  15. #region IEnumerable 成员
  16. //显式实现接口
  17. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  18. {
  19. return GetEnumerator();
  20. }
  21. #endregion
  22. //用字符串初始化列表框
  23. public ListBoxTest(params string[] initialStrings)
  24. {
  25. //为字符串分配内存空间
  26. strings = new String[8];
  27. //复制传递给构造方法的字符串
  28. foreach (string s in initialStrings)
  29. {
  30. strings[ctr++] = s;
  31. }
  32. }
  33. //在列表框最后添加一个字符串
  34. public void Add(string theString)
  35. {
  36. strings[ctr] = theString;
  37. ctr++;
  38. }
  39. //允许数组式的访问
  40. public string this[int index]
  41. {
  42. get {
  43. if (index < 0 || index >= strings.Length)
  44. {
  45. //处理不良索引
  46. }
  47. return strings[index];
  48. }
  49. set {
  50. strings[index] = value;
  51. }
  52. }
  53. //发布拥有的字符串数
  54. public int GetNumEntries()
  55. {
  56. return ctr;
  57. }
  58. }
  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. //创建一个新的列表框并初始化
  6. ListBoxTest lbt = new ListBoxTest("Hello", "World");
  7. //添加新的字符串
  8. lbt.Add("Who");
  9. lbt.Add("Is");
  10. lbt.Add("Douglas");
  11. lbt.Add("Adams");
  12. //测试访问
  13. string subst = "Universe";
  14. lbt[1] = subst;
  15. //访问所有的字符串
  16. foreach (string s in lbt)
  17. {
  18. Console.WriteLine("Value:{0}", s);
  19. }
  20. Console.ReadKey();
  21. }
  22. }

综上所述,一个类型是否支持foreach遍历,必须满足下面条件:

方案1:让这个类实现IEnumerable接口

方案2:这个类有一个public的GetEnumerator的实例方法,并且返回类型中有public 的bool MoveNext()实例方法和public的Current实例属性。

IEnumerator:概念详解的更多相关文章

  1. JWT基础概念详解

    JWT基础概念详解 JWT介绍 之前我们文章讲过分布式session如何存储,其中就讲到过Token.JWT.首先,我们来回顾一下使用Token进行身份认证. 客户端发送登录请求到服务器 服务器在用户 ...

  2. java入门---对象和类&概念详解&实例

        Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载     这篇文章,我们主要来看下: 对象:对象是类的一个实例(对象不是找个女朋友),有状态 ...

  3. Android屏幕密度(Density)和分辨率概念详解

    移动设备有大有小,那么如何适应不同屏幕呢,这给我们编程人员造成了很多困惑.我也是突然想到这些问题,然后去网上搜搜相关东西,整理如下.   首先,对下面这些长度单位必须了解. Android中的长度单位 ...

  4. Storm 学习之路(二)—— Storm核心概念详解

    一.Storm核心概念 1.1 Topologies(拓扑) 一个完整的Storm流处理程序被称为Storm topology(拓扑).它是一个是由Spouts 和Bolts通过Stream连接起来的 ...

  5. Storm 系列(二)—— Storm 核心概念详解

    一.Storm核心概念 1.1 Topologies(拓扑) 一个完整的 Storm 流处理程序被称为 Storm topology(拓扑).它是一个是由 Spouts 和 Bolts 通过 Stre ...

  6. 图像处理术语解释:灰度、色相、饱和度、亮度、明度、阿尔法通道、HSL、HSV、RGBA、ARGB和PRGBA以及Premultiplied Alpha(Alpha预乘)等基础概念详解

    ☞ ░ 前往老猿Python博文目录 ░ 一.引言 由于老猿以前没接触过图像处理,在阅读moviepy代码时,对类的有些处理方法代码看不懂是什么含义,为此花了4天时间查阅了大量资料,并加以自己的理解和 ...

  7. 1-Hyperledger Fabric概念详解

    目录 一.Hyperledger Fabric概述 二.基本术语 1.共享账本ledger 2.通道Channel 3.组织Org 4.智能合约Chaincode 5.背书Endorse 6.各种节点 ...

  8. Spring概念详解

    1.什么是 Spring ? Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2E ...

  9. Maven 专题(五):Maven核心概念详解(一)

    **Maven 的核心程序中仅仅定义了抽象的生命周期,而具体的操作则是由 Maven 的插件来完成的.**可是 Maven 的插件并不包含在 Maven 的核心程序中,在首次使用时需要联网下载. 下载 ...

随机推荐

  1. Linux命令行--使用linux环境变量(转)

    5.1 什么时环境变量 bash shell用一个成为环境变量的特性来存储有关的shell回话和工作环境的信息,这是它们成为环境变量的原因.它允许你在内存中存储数据,以便运行在账户.系统.shell的 ...

  2. glOrtho、glFrustum && glPerspective

    glOrtho         :正交投影,摄像机可以位于裁剪体内,所以near和far可以取两个正值或者一正一负 glFrustum     :透视投影,摄像机不可以位于裁剪体内,所以near和fa ...

  3. ApacheBench(ab)使用详解

    ab命令原理  Apache的ab命令模拟多线程并发请求,测试服务器负载压力,也可以测试nginx.lighthttp.IIS等其它Web服务器的压力.  ab命令对发出负载的计算机要求很低,既不会占 ...

  4. ionic 白名单

    1.在本目录下执行 cordova plugin add cordova-plugin-whitelist 2.在config.xml里添加 <allow-navigation href=&qu ...

  5. 如何解决SQLServer占CPU100%

    文章目录 遇到的问题 使用SQLServer Profiler监控数据库 SQL1:查找最新的30条告警事件 SQL2:获取当前的总报警记录数 有哪些SQL语句会导致CPU过高? 查看SQL的查询计划 ...

  6. Python模块(getpass)

    getpass getpass模块用于输入信息时不显示,比如输入密码时隐藏.getpass模块接收用户的输入的数据类型是str类型. #!/usr/bin/env python #-*- coding ...

  7. 为什么Button点击了没反应,反而其他事件反应了

  8. HttpwebClient的四种请求方式

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷.      本文旨在发布代码,供自己参考,也供大家参考,谢谢. 正题: Ht ...

  9. angular 解析html

    方法1:写filter <div ng-bind-html="showContent | html" class="detail-content"> ...

  10. event对象的属性

    事件类型: bubbles:布尔值,表示事件是否通过DOM以冒泡形式触发. 事件发生时,反应当前环境信息的属性: button: 表示(如果有)鼠标所按下的按钮 ctrlKey: 布尔值,表示Ctrl ...