有人能给我解释一下IEnumerable和IEnumerator吗?

谁能给我解释一下IEnumerableIEnumerator吗?

例如,什么时候用它胜过foreach?IEnumerableIEnumerator之间有什么区别?为什么我们需要使用它?

279901 次浏览

实现IEnumerable可以让你获得一个列表的IEnumerator。

IEnumerator允许使用yield关键字以foreach样式顺序访问列表中的项。

在foreach实现之前(例如在Java 1.4中),迭代列表的方法是从列表中获取一个枚举器,然后向它请求列表中的“下一个”项,只要作为下一个项返回的值不为空。Foreach只是隐式地将其作为一种语言特性来实现,与lock()在幕后实现Monitor类的方式相同。

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

实现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的对象允许其他人访问它的每一项(由枚举员)
  • 实现IEnumerator的对象是在进行迭代。它循环遍历一个可枚举对象。

可以把可枚举对象想象成列表、堆栈、树。

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

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

大多数时候,IEnumerable &IEnumerator被透明地用作foreach循环的一部分。

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

因此,当你的类实现IEnumerable时,你是在说你可以调用一个方法(GetEnumerator)并返回一个新的对象(一个IEnumerator),你可以在循环中使用,比如foreach。

例如,什么时候用它胜过foreach?

你不会在IEnumerable "之上"使用IEnumerable。实现IEnumerable使使用foreach 可能的. 0。

当你写这样的代码时:

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

它在功能上相当于这样写:

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

所谓“功能等效”,我指的是编译器实际将代码转换成的内容。在这个例子中,你不能在baz上使用foreach除非 baz实现了IEnumerable

IEnumerable表示baz实现了该方法

IEnumerator GetEnumerator()

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

bool MoveNext()

而且

Object Current()

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

. net中任何可以迭代的东西都实现了IEnumerable。如果你正在构建自己的类,而且它还没有继承自实现了IEnumerable的类,你可以通过实现IEnumerable(并创建一个它的新GetEnumerator方法将返回的枚举类)来使你的类在foreach语句中可用。

小贡献。

就像他们中的许多人解释“何时使用”和“与foreach一起使用”一样。 我想在这里按要求添加另一个州的区别,关于IEnumerable和IEnumerator之间的差异

我根据下面的讨论线程创建了下面的代码示例。

IEnumerable, IEnumerator vs foreach,什么时候使用什么 IEnumerator和IEnumerable的区别是什么? < / p >

Enumerator保留函数调用之间的状态(迭代位置),而Enumerable则不保留迭代。

下面是带有注释的测试示例。

专家请补充/纠正我。

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());
}
}

IEnumerable和IEnumerator接口

要开始研究实现现有.NET接口的过程,让我们首先看看的角色 IEnumerable和IEnumerator。回想一下,c#支持名为foreach的关键字,它允许您这样做 遍历任何数组类型的内容:

// Iterate over an array of items.
int[] myArrayOfInts = {10, 20, 30, 40};
foreach(int i in myArrayOfInts)
{
Console.WriteLine(i);
}
虽然看起来只有数组类型可以使用这个构造,但问题的真相是 任何支持名为GetEnumerator()方法的类型都可以由foreach构造求值。来 举例说明,跟我来!< / p >

假设我们有一个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接口形式化,该接口隐藏在系统中。集合名称空间。 支持此行为的类或结构宣称它们能够公开所包含的内容 调用者的子项(在本例中为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.
}
如果你想更新Garage类型来支持这些接口,你可以走很长的路 手动实现每个方法。当然,您可以自由地提供自定义版本的 GetEnumerator(), MoveNext(), Current和Reset(),有一个更简单的方法。作为系统。数组类型(以及许多其他集合类)已经实现了IEnumerable和IEnumerator,您可以简单地将请求委托给系统。

.数组
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();
}
}

更新车库类型之后,就可以在c# foreach构造中安全地使用该类型了。此外,假定GetEnumerator()方法是公开定义的,对象用户也可以与IEnumerator类型交互:

// 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的功能,只需使 使用显式接口实现:

IEnumerator IEnumerable.GetEnumerator()
{
// Return the array object's IEnumerator.
return carArray.GetEnumerator();
}
通过这样做,临时对象用户将找不到Garage的GetEnumerator()方法,而 Foreach构造将在必要时在后台获取接口

改编自Pro c# 5.0和。net 4.5框架

IEnumerable和IEnumerator的区别:

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

    IEnumerable有一个方法GetEnumerator() < / p >

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

IEnumerator有一个名为Current的属性和两个方法,Reset()和MoveNext()(这对于了解列表中项目的当前位置非常有用)

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

我注意到了这些不同之处:

A.我们以不同的方式迭代列表,foreach可用于IEnumerable, while loop可用于IEnumerator。

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

理解Iterator模式将对您有所帮助。我建议你也读一下。

迭代器模式

在较高的级别上,迭代器模式可用于提供一种遍历任何类型集合的标准方法。 迭代器模式中有3个参与者,实际的集合(客户端)、聚合器和迭代器。聚合是一个接口/抽象类,具有返回迭代器的方法。Iterator是一个接口/抽象类,它具有允许我们遍历集合的方法。< / p > 为了实现该模式,我们首先需要实现一个迭代器,以生成一个具体的迭代器,该迭代器可以遍历相关的集合(客户端) 然后集合(客户端)实现聚合器以返回上述迭代器的实例 这是UML图 Iterator Pattern < / p >

在c#中,IEnumerable是抽象的集合IEnumerator是抽象的迭代器。IEnumerable有一个单独的方法GetEnumerator,它负责创建所需类型的IEnumerator实例。像list这样的集合实现了IEnumerable。

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

的实例

为了计算排列的数量,我们可以像下面这样做。

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

c#编译器或多或少地将上面的转换为

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

如果你有任何问题,请尽管问。

通过类比+代码演练进行解释

类比:你是一架飞机上的中央情报局特工。你想知道乘客的信息,也就是说,你需要“穿越”;这样你就能逮捕嫌疑人了

  • 你从飞机前部开始,然后一直走到后部,询问乘客:他们是谁,从哪里来等等。

一架飞机只能这样做,如果它是:

  1. 可数名词,
  2. 如果它有计数器。

因为这是接口所需要的。

(如果这是信息过载,你所需要知道的就是你想要能够问每个乘客一些问题)

可数是什么意思?

如果一家航空公司是“可数的”,这意味着飞机上必须有一名空乘人员,他的唯一工作就是计数——而且这位空乘人员必须以一种非常具体的方式计数:

  1. 柜台/空乘人员必须在第一位乘客之前开始(在每个人的前面演示安全,如何穿救生衣等)。
  2. 他/她(即空乘)必须“下一个移动”;沿着过道走到第一个座位。
  3. 然后他/她要记录:(i)坐在座位上的人,以及(ii)他们目前在过道上的位置。

计算程序

航空公司的机长要每一位乘客在接受调查时的报告。因此,在与第一个座位的乘客交谈后,空乘人员/柜台人员就会向机长报告,当机长报告后,柜台人员会记住他/她在过道中的确切位置,并继续从他/她停下来的地方数到他/她。

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

  • 计数器一直往前走,直到他到达飞机的尽头。

让我们把它和IEnumerables联系起来

  • 枚举对象只是飞机上乘客的集合。民用航空法-这些是所有IEnumerables必须遵守的基本规则。每次空乘人员带着乘客信息去找机长时,我们基本上是在把乘客“交给”机长。机长基本上可以对乘客做任何他想做的事——除了在飞机上重新安排乘客。在这种情况下,如果他们追随曼城(Manchester City),就会得到优待(啊!)

     foreach (Passenger passenger 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
    { // <---------- Note the curly bracket that is here.
    // 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 (passenger.supports_mancestercity())
    {
    passenger.getFreeChampaign();
    } else
    {
    // you get nothing! GOOD DAY SIR!
    }
    } //  <---- Note the curly bracket that is here!
    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)
    

总结

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

可枚举的只是“可数的”的一个花哨的词。换句话说,一个可枚举对象允许你“枚举”(即计数)。

IEnumerable和IEnumerator(以及它们的泛型对应物IEnumerable<T>和IEnumerator<T>)是.Net框架类库集合迭代器实现的基本接口。

IEnumerable是你在大多数代码中看到的最常见的接口。它支持foreach循环,生成器(想想收益率),因为它的小接口,它被用来创建紧密的抽象。IEnumerable依赖于IEnumerator

另一方面,IEnumerator提供了一个稍低级别的迭代接口。它被称为显式迭代器,它让程序员对迭代周期有更多的控制。

IEnumerable

IEnumerable是一个标准接口,可以在支持它的集合上迭代(事实上,我今天能想到的所有集合类型都实现了IEnumerable)。编译器支持允许像foreach这样的语言特性。一般来说,它启用了隐式迭代器实现

foreach循环

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

我认为foreach循环是使用IEnumerable接口的主要原因之一。与经典的C风格的循环相比,foreach具有非常简洁的语法,非常容易理解,在循环中你需要检查各种变量来查看它在做什么。

生成的关键字

可能一个不太为人所知的特性是,IEnumerable还通过使用yield returnyield break语句启用了c#生成器

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

抽象

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

问题

需要注意的一种行为是,在流实现中(例如,从数据库中逐行检索数据,而不是先将所有结果加载到内存中),不能将对集合迭代不止一次。这与像列表这样的内存集合相反,在那里你可以迭代多次而没有问题。ReSharper,例如有一个代码检查可能的多重枚举IEnumerable

IEnumerator

另一方面,IEnumerator是使IEnumerble-foreach-magic工作的幕后接口。严格来说,它启用了显式迭代器。

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

根据我的经验,IEnumerator很少在常见场景中使用,因为它的语法更啰嗦,语义有点混乱(至少对我来说;例如,MoveNext ()也返回一个值,这个名称根本没有暗示)。

IEnumerator的用例

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

客户端代码

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;
}
}
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
*/

IEnumerableIEnumerator都是c#中的接口。

IEnumerable是一个接口,定义了一个返回IEnumerator接口的方法GetEnumerator()

这适用于对集合的只读访问,该集合实现了IEnumerable可以与foreach语句一起使用。

IEnumerator有两个方法,MoveNextReset。它还有一个名为Current的属性。

下面展示了IEnumerable和IEnumerator的实现。

IEnumerable是一个包含Ienumerator的框。IEnumerable是所有集合的基本接口。如果集合实现了IEnumerable,则foreach循环可以运行。在下面的代码中,它解释了拥有自己的枚举器的步骤。让我们首先定义我们要创建集合的Class。

public class Customer
{
public String Name { get; set; }
public String City { get; set; }
public long Mobile { get; set; }
public double Amount { get; set; }
}

现在我们将定义Class,它将作为我们的类Customer的集合。注意,它实现了接口IEnumerable。所以我们必须实现GetEnumerator方法。这将返回我们的自定义枚举器。

public class CustomerList : IEnumerable
{
Customer[] customers = new Customer[4];
public CustomerList()
{
customers[0] = new Customer { Name = "Bijay Thapa", City = "LA", Mobile = 9841639665, Amount = 89.45 };
customers[1] = new Customer { Name = "Jack", City = "NYC", Mobile = 9175869002, Amount = 426.00 };
customers[2] = new Customer { Name = "Anil min", City = "Kathmandu", Mobile = 9173694005, Amount = 5896.20 };
customers[3] = new Customer { Name = "Jim sin", City = "Delhi", Mobile = 64214556002, Amount = 596.20 };
}


public int Count()
{
return customers.Count();
}
public Customer this[int index]
{
get
{
return customers[index];
}
}
public IEnumerator GetEnumerator()
{
return customers.GetEnumerator(); // we can do this but we are going to make our own Enumerator
return new CustomerEnumerator(this);
}
}

现在我们要创建自己的自定义枚举器,如下所示。我们必须实现方法MoveNext。

 public class CustomerEnumerator : IEnumerator
{
CustomerList coll;
Customer CurrentCustomer;
int currentIndex;
public CustomerEnumerator(CustomerList customerList)
{
coll = customerList;
currentIndex = -1;
}


public object Current => CurrentCustomer;


public bool MoveNext()
{
if ((currentIndex++) >= coll.Count() - 1)
return false;
else
CurrentCustomer = coll[currentIndex];
return true;
}


public void Reset()
{
// we dont have to implement this method.
}
}

现在我们可以像下面这样对我们的集合使用foreach循环;

    class EnumeratorExample
{
static void Main(String[] args)
{


CustomerList custList = new CustomerList();
foreach (Customer cust in custList)
{
Console.WriteLine("Customer Name:"+cust.Name + " City Name:" + cust.City + " Mobile Number:" + cust.Amount);
}
Console.Read();


}
}