任何人都可以解释IEnumerable和IEnumerator给我?

任何人都可以解释IEnumerable和IEnumerator给我?

比如什么时候用foreach呢? IEnumerable和IEnumerator有什么区别? 为什么我们需要使用它?

比如什么时候用foreach呢?

您不要使用IEnumerable “over” foreach 。 实现IEnumerable使得使用foreach 可能

当你写代码如:

 foreach (Foo bar in baz) { ... } 

它在function上等同于书写:

 IEnumerator bat = baz.GetEnumerator(); while (bat.MoveNext()) { bar = (Foo)bat.Current ... } 

通过“function等同”,我的意思是这实际上是编译器将代码转换成的东西。 除非 baz实现了IEnumerable 否则你不能在这个例子中使用对baz foreach

IEnumerable意味着baz实现该方法

 IEnumerator GetEnumerator() 

该方法返回的IEnumerator对象必须实现这些方法

 bool MoveNext() 

 Object Current() 

第一个方法前进到创build枚举数的IEnumerable对象中的下一个对象,如果完成则返回false ,第二个方法返回当前对象。

.Net中的任何可以迭代的实现IEnumerable 。 如果您正在构build自己的类,并且它尚未从实现IEnumerable的类inheritance,那么可以通过实现IEnumerable (并创build一个新的GetEnumerator方法将返回的枚举类)使您的类在foreach语句中可用, 。

IEnumerable和IEnumerator接口

要开始检查实现现有的.NET接口的过程,我们先来看看IEnumerable和IEnumerator的作用。 回想一下C#支持一个名为foreach的关键字,它允许你迭代任何数组types的内容:

 // Iterate over an array of items. int[] myArrayOfInts = {10, 20, 30, 40}; foreach(int i in myArrayOfInts) { Console.WriteLine(i); } 

虽然看起来只有数组types可以使用这个构造,但事实的真相是任何支持名为GetEnumerator()的方法都可以通过foreach构造来评估。为了说明,请跟随我!

假设我们有一个Garage类:

 // Garage contains a set of Car objects. public class Garage { private Car[] carArray = new Car[4]; // Fill with some Car objects upon startup. public Garage() { carArray[0] = new Car("Rusty", 30); carArray[1] = new Car("Clunker", 55); carArray[2] = new Car("Zippy", 30); carArray[3] = new Car("Fred", 30); } } 

理想情况下,使用foreach构造迭代Garage对象的子项将会很方便,就像数组值一样:

 // This seems reasonable ... public class Program { static void Main(string[] args) { Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n"); Garage carLot = new Garage(); // Hand over each car in the collection? foreach (Car c in carLot) { Console.WriteLine("{0} is going {1} MPH", c.PetName, c.CurrentSpeed); } Console.ReadLine(); } } 

不幸的是,编译器告诉你Garage类没有实现一个名为GetEnumerator()的方法。 这个方法是通过IEnumerable接口来forms化的,它存在于System.Collections命名空间中。 支持这种行为的类或结构宣称他们能够将包含的子项展现给调用者(在本例中是foreach关键字本身)。 这里是这个标准的.NET接口的定义:

 // This interface informs the caller // that the object's subitems can be enumerated. public interface IEnumerable { IEnumerator GetEnumerator(); } 

如您所见,GetEnumerator()方法返回对另一个名为System.Collections.IEnumerator的接口的引用。 此接口提供的基础结构允许调用者遍历由IEnumerable兼容容器包含的内部对象:

 // This interface allows the caller to // obtain a container's subitems. public interface IEnumerator { bool MoveNext (); // Advance the internal position of the cursor. object Current { get;} // Get the current item (read-only property). void Reset (); // Reset the cursor before the first member. } 

如果要更新车库types以支持这些接口,则可以采取漫长的道路并手动实施每种方法。 虽然您肯定可以自由提供GetEnumerator(),MoveNext(),Current和Reset()的自定义版本,但还是有一个更简单的方法。 由于System.Arraytypes(以及其他许多集合类)已经实现了IEnumerable和IEnumerator,因此可以简单地将请求委托给System.Array,如下所示:

 using System.Collections; ... public class Garage : IEnumerable { // System.Array already implements IEnumerator! private Car[] carArray = new Car[4]; public Garage() { carArray[0] = new Car("FeeFee", 200); carArray[1] = new Car("Clunker", 90); carArray[2] = new Car("Zippy", 30); carArray[3] = new Car("Fred", 30); } public IEnumerator GetEnumerator() { // Return the array object's IEnumerator. return carArray.GetEnumerator(); } } 

在更新了Garagetypes之后,可以安全地使用C#foreach构造中的types。 此外,鉴于GetEnumerator()方法已经公开定义,对象用户也可以与IEnumeratortypes进行交互:

 // Manually work with IEnumerator. IEnumerator i = carLot.GetEnumerator(); i.MoveNext(); Car myCar = (Car)i.Current; Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed); 

但是,如果您希望从对象级隐藏IEnumerable的function,只需使用显式接口实现:

 IEnumerator IEnumerable.GetEnumerator() { // Return the array object's IEnumerator. return carArray.GetEnumerator(); } 

通过这样做,临时对象用户将不会findGarage的GetEnumerator()方法,而foreach构造将在必要时在后台获取接口。

改编自Pro C#5.0和.NET 4.5 Framework

实现IEnumerable意味着你的类返回一个IEnumerator对象:

 public class People : IEnumerable { IEnumerator IEnumerable.GetEnumerator() { // return a PeopleEnumerator } } 

实现IEnumerator意味着你的类返回迭代的方法和属性:

 public class PeopleEnumerator : IEnumerator { public void Reset()... public bool MoveNext()... public object Current... } 

无论如何,这是不同的。

IEnumerable实现GetEnumerator。 被调用时,该方法将返回一个实现MoveNext,Reset和Current的IEnumerator 。

因此,当你的类实现IEnumerable时,你说你可以调用一个方法(GetEnumerator)并获得一个新的对象(一个IEnumerator),你可以在foreach这样的循环中使用。

我会尽量解释,而不使用任何代码,然后我会在稍后添加它。

假设你正在经营一家航空公司。 在每架飞机上,你都想知道飞机上乘客的信息。 基本上你想能够“穿越”飞机。 换句话说,你希望能够从前排座位开始,然后朝飞机后方方向飞行,向乘客询问一些信息:他们是谁,他们来自哪里等。飞机只能做到这一点根据IEnumerable航空法,如果是:

  1. 可数,和
  2. 如果它有一个柜台。

可数是什么意思?

如果一家航空公司是“可数”的,这意味着必须有一名乘务员出现在飞机上,唯一的工作就是数数。 这位乘务员(又名“柜台”)必须按照规定的方式计算:

  1. 柜台必须在第一位乘客之前开始。
  2. 然后,他必须“移动”到第一个座位的过道。
  3. 他将logging:(i)该人在座的人,以及(ii)他目前在过道的位置。

计数程序

航空公司的船长希望在每个旅客被调查或计算时提供一份报告。 所以,在和第一个座位的人说话之后,柜台就向上尉报告,当报告给出时,柜台记得他在过道上的确切位置,并在他离开的地方继续计数。

以这种方式,船长总是能够获得关于当前被调查人员的信息。 那样的话,如果他发现这个人喜欢曼城,那么他可以给这个乘客优惠等等。

  • 柜台继续前进,直到他到达飞机的尽头。

让我们把这与IEnumerables

  • 枚举只是飞机上乘客的集合。 民航法 – 这些基本上是所有IE编号必须遵循的规则。 每次航空公司的乘务员都带着乘客信息去上尉,我们基本上是把乘客送给上尉。 机长基本上可以为乘客做他想做的事情 – 除了在飞机上重新安排乘客。 在这种情况下,如果他们跟随曼城(呃!),他们会得到优惠待遇

     foreach (Passenger p in Plane) // the airline hostess is now at the front of the plane // and slowly making her way towards the back // when she get to a particular passenger she gets some information // about the passenger and then immediately heads to the cabin // to let the captain decide what to do with it { // we are now cockpit of the plane with the captain. // the captain wants to give the passenger free // champaign if they support manchester city if (p.supports_mancestercity()) { p.getFreeChampaign(); } else { // you get nothing! GOOD DAY SIR! } } // the hostess has delivered the information to the captain and goes to the next person on the plane (if she has not reached the end of the plane) 

概要

换句话说,如果它有一个计数器 ,那么就是可数 。 计数器必须(基本上):(i)记住它的位置( 状态 ),(ii)能够移动 ,(iii)并且知道他正在处理的当前人。

可枚举只是“可数”的一个奇特的词。 换句话说,枚举允许你“枚举”。

我希望这是有道理的? 现在,如果你阅读上面的答案,你应该有更多的一般概念是怎么回事。

实现IEnumerable使您能够获得列表的IEnumerator。

IEnumerator允许使用yield关键字对列表中的项目进行foreach style顺序访问。

在实现之前(例如在Java 1.4中),迭代列表的方法是从列表中获取一个枚举器,然后要求列表中的“下一个”项,只要返回的值为下一个项目不为空。 Foreach只是将其隐式地作为一种语言function,就像lock()在后台实现Monitor类一样。

我期望foreach在列表上工作,因为他们实现IEnumerable。

  • 一个实现IEnumerable的对象允许其他人访问它的每一个项目(通过一个枚举器)
  • 一个实现IEnumerator的对象就是这样做的迭代。 它遍历一个枚举对象。

想象列表,堆栈,树等枚举对象。

实现IEnumerable本质上意味着对象可以迭代。 这并不一定意味着它是一个数组,因为有某些列表不能被索引,但可以枚举它们。

IEnumerator是用于执行迭代的实际对象。 它控制从列表中的一个对象移动到下一个对象。

大多数情况下, IEnumerableIEnumerator被透明地用作foreach循环的一部分。

IEnumerable和IEnumerator(以及它们的通用对象IEnumerable <T>和IEnumerator <T>)是.Net Framework Class Libray集合中迭代器实现的基础接口。

IEnumerable是大多数代码中最常见的界面。 它使得foreach循环,生成器(思考yield )以及由于它的小界面,它被用来创build紧密的抽象。 IEnumerable依赖于IEnumerator 。

IEnumerator ,另一方面,提供了一个稍微低一级的迭代界面。 它被称为显式迭代器 ,它使程序员能更好地控制迭代周期。

IEnumerable的

IEnumerable是一个标准接口,可以对支持它的集合进行迭代(事实上,我今天可以想到的所有集合types都实现了IEnumerable )。 编译器支持允许像foreach这样的语言function。 一般而言,它使这个隐式的迭代器实现 。

foreach循环

 foreach (var value in list) Console.WriteLine(value); 

我认为foreach循环是使用IEnumerable接口的主要原因之一。 与传统C风格的循环相比, foreach有一个非常简洁的语法,并且非常容易理解,您需要检查各种variables以查看它在做什么。

yield关键字

可能是一个鲜为人知的特性是, IEnumerable也使C#中的生成器能够使用yield returnyield break语句。

 IEnumerable<Thing> GetThings() { if (isNotReady) yield break; while (thereIsMore) yield return GetOneMoreThing(); } 

抽象

在实践中另一个常见的情况是使用IEnumerable来提供极简抽象。 因为它是一个小型且只读的接口,所以鼓励您将集合公开为IEnumerable (而不是List )。 这样,你可以自由地改变你的实现,而不会破坏你的客户端的代码(例如,将List更改为LinkedList )。

疑难杂症

需要注意的一个行为是在stream实现中(例如,从数据库中逐行检索数据,而不是先将所有结果加载到内存中), 则不能多次迭代集合。 这与List之类的内存集合形成鲜明对比,在这种集合中,可以多次迭代而不会出现任何问题。 例如,ReSharper 有一个代码检查IEnumerable的可能的多个枚举

IEnumerator的

另一方面,IEnumerator是IEnumeble-foreach-magic工作的幕后界面。 严格地说,它启用显式迭代器。

 var iter = list.GetEnumerator(); while (iter.MoveNext()) Console.WriteLine(iter.Current); 

根据我的经验, IEnumerator很less在常见场景中使用,因为它的语法比较冗长,语义略有混乱(至less对于我来说,例如MoveNext()也会返回一个值,而这个名字根本就没有提示)。

IEnumerator用例

我只使用IEnumerator ,特别是(稍低级别)的库和框架,我提供了IEnumerable接口。 一个例子是一个数据stream处理库,它提供了在foreach循环中的一系列对象,尽pipe后台数据是使用各种文件stream和序列化来收集的。

客户端代码

 foreach(var item in feed.GetItems()) Console.WriteLine(item); 

图书馆

 IEnumerable GetItems() { return new FeedIterator(_fileNames) } class FeedIterator: IEnumerable { IEnumerator GetEnumerator() { return new FeedExplicitIterator(_stream); } } class FeedExplicitIterator: IEnumerator { DataItem _current; bool MoveNext() { _current = ReadMoreFromStream(); return _current != null; } DataItem Current() { return _current; } } 

IEnumerable和IEnumerator之间的区别:

  • IEnumerable在内部使用IEnumerator。
  • IEnumerable不知道哪个项目/对象正在执行。
  • 每当我们将IEnumerator传递给另一个函数时,它就知道item / object的当前位置。
  • 每当我们将IEnumerable集合传递给另一个函数时,它不知道项目/对象的当前位置(不知道它正在执行哪个项目)

    IEnumerable有一个方法GetEnumerator()

 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } 

IEnumerator有一个属性current和两个方法Reset和MoveNext(这对于了解列表中某个项目的当前位置很有用)。

 public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); } 

小额捐款。

其中许多解释了“何时使用”和“与foreach一起使用”。 我想在这里添加另一个国家的差异 ,因为两个IEnumerable IEnumerator之间的区别。

我基于下面的讨论线程创build了下面的代码示例。

IEnumerable,IEnumerator与foreach什么时候用什么 IEnumerator和IEnumerable 有 什么区别?

枚举器保留了函数调用之间的状态(迭代位置),而迭代另一方面Enumerable没有。

这里是testing的例子与意见理解。

专家请加/纠正我。

 static void EnumerableVsEnumeratorStateTest() { IList<int> numList = new List<int>(); numList.Add(1); numList.Add(2); numList.Add(3); numList.Add(4); numList.Add(5); numList.Add(6); Console.WriteLine("Using Enumerator - Remembers the state"); IterateFrom1to3(numList.GetEnumerator()); Console.WriteLine("Using Enumerable - Does not Remembers the state"); IterateFrom1to3Eb(numList); Console.WriteLine("Using Enumerable - 2nd functions start from the item 1 in the collection"); } static void IterateFrom1to3(IEnumerator<int> numColl) { while (numColl.MoveNext()) { Console.WriteLine(numColl.Current.ToString()); if (numColl.Current > 3) { // This method called 3 times for 3 items (4,5,6) in the collection. // It remembers the state and displays the continued values. IterateFrom3to6(numColl); } } } static void IterateFrom3to6(IEnumerator<int> numColl) { while (numColl.MoveNext()) { Console.WriteLine(numColl.Current.ToString()); } } static void IterateFrom1to3Eb(IEnumerable<int> numColl) { foreach (int num in numColl) { Console.WriteLine(num.ToString()); if (num>= 5) { // The below method invokes for the last 2 items. //Since it doesnot persists the state it will displays entire collection 2 times. IterateFrom3to6Eb(numColl); } } } static void IterateFrom3to6Eb(IEnumerable<int> numColl) { Console.WriteLine(); foreach (int num in numColl) { Console.WriteLine(num.ToString()); } } 

我注意到这些差异:

答:我们以不同的方式迭代列表,foreach可以用于IEnumerable和IEnumerator的while循环。

B. IEnumerator可以记住当前的索引,当我们从一个方法传递到另一个(它开始使用当前索引),但IEnumerable不记得索引,它将索引重置为开始。 更多在这个videohttps://www.youtube.com/watch?v=jd3yUjGc9M0

了解Iterator模式将对您有所帮助。 我build议阅读相同的。

迭代器模式

在高层次上,迭代器模式可以用来提供一种迭代任何types集合的标准方法。 我们有3个参与者在迭代器模式,实际的集合(客户端),聚合器和迭代器。 聚合是一个接口/抽象类,它有一个返回迭代器的方法。 Iterator是一个接口/抽象类,它有方法允许我们迭代一个集合。

为了实现这个模式,我们首先需要实现一个迭代器来生成一个可以遍历相关集合(客户端)的具体实现。然后集合(客户端)实现聚合器来返回上述迭代器的一个实例。

这是UML图 迭代器模式

所以基本上在C#中,IEnumerable是抽象聚合,IEnumerator是抽象Iterator。 IEnumerable有一个方法GetEnumerator负责创build所需types的IEnumerator实例。 像列表的集合实现了IEnumerable。

例。 让我们假设我们有一个方法getPermutations(inputString) ,它返回一个string的所有排列,并且该方法返回一个IEnumerable<string>的实例IEnumerable<string>

为了计算排列的数量,我们可以做如下的事情。

  int count = 0; var permutations = perm.getPermutations(inputString); foreach (string permutation in permutations) { count++; } 

C#编译器或多或less地将上述内容转换为

 using (var permutationIterator = perm.getPermutations(input).GetEnumerator()) { while (permutationIterator.MoveNext()) { count++; } } 

如果您有任何问题,请不要犹豫,问。

 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Enudemo { class Person { string name = ""; int roll; public Person(string name, int roll) { this.name = name; this.roll = roll; } public override string ToString() { return string.Format("Name : " + name + "\t Roll : " + roll); } } class Demo : IEnumerable { ArrayList list1 = new ArrayList(); public Demo() { list1.Add(new Person("Shahriar", 332)); list1.Add(new Person("Sujon", 333)); list1.Add(new Person("Sumona", 334)); list1.Add(new Person("Shakil", 335)); list1.Add(new Person("Shruti", 336)); } IEnumerator IEnumerable.GetEnumerator() { return list1.GetEnumerator(); } } class Program { static void Main(string[] args) { Demo d = new Demo(); // Notice here. it is simple object but for //IEnumerator you can get the collection data foreach (Person X in d) { Console.WriteLine(X); } Console.ReadKey(); } } } /* Output : Name : Shahriar Roll : 332 Name : Sujon Roll : 333 Name : Sumona Roll : 334 Name : Shakil Roll : 335 Name : Shruti Roll : 336 */ 
 using System; using System.Collections; namespace IEnumerableDemo { class Demo { int[] a = { 1, 2, 3, 4, 5 }; public IEnumerator GetEnumerator() { return a.GetEnumerator(); } } class Program { static void Main(string[] args) { Demo d = new Demo(); // Here we can use object for array data //by IEnumerable foreach (int X in d) { Console.WriteLine(X); } Console.ReadKey(); } } } /* Output: 1 2 3 4 5 */ 
 using System; using System.Collections; namespace IEnumerableDemo { class Demo { int[] a = { 1, 2, 3, 4, 5 }; public IEnumerator GetEnumerator() { foreach (int X in a) // or you can use this version { // it will produce same output yield return X; } } } class Program { static void Main(string[] args) { Demo d = new Demo(); foreach (int X in d) { Console.WriteLine(X); } Console.ReadKey(); } } } /* Output: 1 2 3 4 5 */