IEnumerable和IEnumerator 详解

摘要:
初学C#的时候,老是被IEnumerable、IEnumerator、ICollection等这样的接口弄的糊里糊涂,我觉得有必要切底的弄清楚IEnumerable和IEnumerator的本质。下面我们先看IEnumerable和IEnumerator两个接口的语法定义。其实IEnumerable接口是非常的简单,只包含一个抽象的方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象。这个接口提供了基础设施,调用方可以用来移动IEnumerable兼容容器包含的内部对象。

初学C#的时候,老是被IEnumerable、IEnumerator、ICollection等这样的接口弄的糊里糊涂,我觉得有必要切底的弄清楚IEnumerable和IEnumerator的本质。

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

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

IEnumerable和IEnumerator 详解第1张

IEnumerable和IEnumerator 详解第2张

详细讲解:

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

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

//遍历数组的项

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

foreach(int i in my myArrayOfInts)

{

Console.WirteLine(i);

}

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

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

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

  1. //这看起来好像是可行的
  2. lassProgram
  3. {
  4. staticvoidMain(string[]args)
  5. {
  6. Console.WriteLine("*********FunwithIEnumberable/IEnumerator************\n");
  7. GaragecarLot=newGarage();
  8. //交出集合中的每一Car对象吗
  9. foreach(CarcincarLot)
  10. {
  11. Console.WriteLine("{0}isgoing{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. namespaceMyCarIEnumerator
  2. {
  3. publicclassGarage:IEnumerable
  4. {
  5. Car[]carArray=newCar[4];
  6. //启动时填充一些Car对象
  7. publicGarage()
  8. {
  9. carArray[0]=newCar("Rusty",30);
  10. carArray[1]=newCar("Clunker",50);
  11. carArray[2]=newCar("Zippy",30);
  12. carArray[3]=newCar("Fred",45);
  13. }
  14. publicIEnumeratorGetEnumerator()
  15. {
  16. returnthis.carArray.GetEnumerator();
  17. }
  18. }
  19. }
  20. //修改Garage类型之后,就可以在C#foreach结构中安全使用该类型了。
  1. //除此之外,GetEnumerator()被定义为公开的,对象用户可以与IEnumerator类型交互:
  2. namespaceMyCarIEnumerator
  3. {
  4. classProgram
  5. {
  6. staticvoidMain(string[]args)
  7. {
  8. Console.WriteLine("*********FunwithIEnumberable/IEnumerator************\n");
  9. GaragecarLot=newGarage();
  10. //交出集合中的每一Car对象吗
  11. foreach(CarcincarLot)//之所以遍历carLot,是因为carLot.GetEnumerator()返回的项时Car类型,这个十分重要
  12. {
  13. Console.WriteLine("{0}isgoing{1}MPH",c.CarName,c.CurrentSpeed);
  14. }
  15. Console.WriteLine("GetEnumerator被定义为公开的,对象用户可以与IEnumerator类型交互,下面的结果与上面是一致的");
  16. //手动与IEnumerator协作
  17. IEnumeratori=carLot.GetEnumerator();
  18. while(i.MoveNext())
  19. {
  20. CarmyCar=(Car)i.Current;
  21. Console.WriteLine("{0}isgoing{1}MPH",myCar.CarName,myCar.CurrentSpeed);
  22. }
  23. Console.ReadLine();
  24. }
  25. }
  26. }

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

  1. namespaceForeachTestCase
  2. {
  3. //继承IEnumerable接口,其实也可以不继承这个接口,只要类里面含有返回IEnumberator引用的GetEnumerator()方法即可
  4. classForeachTest:IEnumerable{
  5. privatestring[]elements;//装载字符串的数组
  6. privateintctr=0;//数组的下标计数器
  7. ///<summary>
  8. ///初始化的字符串
  9. ///</summary>
  10. ///<paramname="initialStrings"></param>
  11. ForeachTest(paramsstring[]initialStrings)
  12. {
  13. //为字符串分配内存空间
  14. elements=newString[8];
  15. //复制传递给构造方法的字符串
  16. foreach(stringsininitialStrings)
  17. {
  18. elements[ctr++]=s;
  19. }
  20. }
  21. ///<summary>
  22. ///构造函数
  23. ///</summary>
  24. ///<paramname="source">初始化的字符串</param>
  25. ///<paramname="delimiters">分隔符,可以是一个或多个字符分隔</param>
  26. ForeachTest(stringinitialStrings,char[]delimiters)
  27. {
  28. elements=initialStrings.Split(delimiters);
  29. }
  30. //实现接口中得方法
  31. publicIEnumeratorGetEnumerator()
  32. {
  33. returnnewForeachTestEnumerator(this);
  34. }
  35. privateclassForeachTestEnumerator:IEnumerator
  36. {
  37. privateintposition=-1;
  38. privateForeachTestt;
  39. publicForeachTestEnumerator(ForeachTestt)
  40. {
  41. this.t=t;
  42. }
  43. #region实现接口
  44. publicobjectCurrent
  45. {
  46. get
  47. {
  48. returnt.elements[position];
  49. }
  50. }
  51. publicboolMoveNext()
  52. {
  53. if(position<t.elements.Length-1)
  54. {
  55. position++;
  56. returntrue;
  57. }
  58. else
  59. {
  60. returnfalse;
  61. }
  62. }
  63. publicvoidReset()
  64. {
  65. position=-1;
  66. }
  67. #endregion
  68. }
  69. staticvoidMain(string[]args)
  70. {
  71. //ForeachTestf=newForeachTest("Thisisasamplesentence.",newchar[]{'','-'});
  72. ForeachTestf=newForeachTest("This","is","a","sample","sentence.");
  73. foreach(stringiteminf)
  74. {
  75. System.Console.WriteLine(item);
  76. }
  77. Console.ReadKey();
  78. }
  79. }
  80. }

IEnumerable<T>接口

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

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

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

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

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

免责声明:文章转载自《IEnumerable和IEnumerator 详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Unity UGUI-Raw Image 组件(生图/未加工图片)oracle 迁移到clickhouse 45亿条数据下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

delphi 集合类型探究

集合是由具有某些共同特征的元素构成的一个整体。在pascal中,一个集合是由具有同一有序类型的一组数据元素所组成,这一有序类型称为该集合的基类型。  一、集合类型的定义和变量的说明  集合类型的一般形式为:    set of 基类型;  基类型可以是任意顺序类型, 而不能是实型或其它构造类型。同时,基类型的数据的序号不得超过255。例如下列说明是合法的:...

js 过滤数组里对象的某个属性

需求 一个大数组,里面乱七八糟一堆属性。现在只需要其中两个属性,用这两个属性组成一个对象,重新得到一个新数组。新数组长度跟老数组一样,只不过里面所有对象只剩下了两个属性 示例 handleSelectionChange(selection) { var invoiceGxQueryList = []; for (var i =...

关于C++里set_intersection(取集合交集)、set_union(取集合并集)、set_difference(取集合差集)等函数的使用总结

文章转载自https://blog.csdn.net/zangker/article/details/22984803 set里面有set_intersection(取集合交集)、set_union(取集合并集)、set_difference(取集合差集)、set_symmetric_difference(取集合对称差集)等函数。其中,关于函数的五个参数问...

Scala(七)集合

7.1集合简介   1)说明 (1)Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质。 (2)对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本,分别位于以下两个包 不可变集合:scala.collection.immutable 可变集合:scala.collection.mutable...

[转发]for 循环,jQuery循环遍历详解

1.for 循环原生JS最基本的使用: for (var i=0;i<cars.length;i++) { ..... } for - 循环代码块一定的次数2.for infor/in - 循环遍历对象的属性以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行 var obj = {a:1, b:2, c:3}; f...

Windows下重叠I/O模型

一.重叠模型的优点 1.可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。 2.比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。 因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发...