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. 热门WEB前端职业你只需要掌握这些

    在知名的互联网企业里工作是一件很美好的事情,有很多的工作机会,而且企业们通过高薪以及令人羡慕的福利来争夺最优秀的人才.但是如果你花了大量的时间在招聘网站上和公司的帖子上,你可能会注意到在网页设计这个工 ...

  2. 计算机管理打不开的解决方法,直接cmd修改reg

    复制命令即可: reg add HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\she ...

  3. robotframework数据驱动框架

    即将更新...............

  4. Bash中的shopt选项

    Bash中的shopt选项 http://blog.chinaunix.net/uid-20587169-id-1919110.html shopt命令用于显示和设置shell中的行为选项,通过这些选 ...

  5. you need to upgrade the working copy first

    is too old (format 29) to work with client version '1.9.4 (r1740329)' (expects format 31) 2016年09月18 ...

  6. Apache配置文件httpd.conf内容翻译

      本文已经废弃,现在apache2不依靠httpd.conf来配置. Ubuntu下默认的配置文件是/etc/apache2/sites-available/default 可以修改上面文件来修改a ...

  7. VMware设置共享文件夹

    第一步:安装vmware-tools

  8. 笔记:ASP.NET MVC安全

    XSRF/CSRF Prevention in MVC ValidateAntiForgeryToken 参考这里的简单例子:http://www.asp.net/mvc/tutorials/mvc- ...

  9. 记32位程序(使用3gb用户虚拟内存)使用D3DX9导致的一个崩溃的问题

    为了增加32位程序的用户虚拟内存的使用量,我们使用了/LARGEADDRESSAWARE编译选项来使32位程序可能使用到3gb的内存,能否使用到3gb内存也跟平台.系统和设置有关系,现摘抄部分作为参考 ...

  10. arping 通知网关刷新IP

     arping -c 2 -I em1 -s 192.168.110.12  192.168.110.1