任何人都可以向我解释IEnumerable和IEnumerator吗?
例如,什么时候在foreach上使用它? IEnumerable和IEnumerator之间有什么区别?为什么我们需要使用它?答案 0 :(得分:239)
例如,何时在foreach上使用它?
您不要使用IEnumerable
“而不是foreach
。实施IEnumerable
会使foreach
成为可能。
当您编写如下代码时:
foreach (Foo bar in baz)
{
...
}
它在功能上等同于写作:
IEnumerator bat = baz.GetEnumerator();
while (bat.MoveNext())
{
bar = (Foo)bat.Current
...
}
通过“功能等效”,我的意思是编译器实际上将代码转换为什么。除非 foreach
实现baz
,否则您无法在此示例中使用baz
上的IEnumerable
。
IEnumerable
表示baz
实现了方法
IEnumerator GetEnumerator()
此方法返回的IEnumerator
对象必须实现方法
bool MoveNext()
和
Object Current()
第一种方法前进到创建枚举器的IEnumerable
对象中的下一个对象,如果完成则返回false
,第二种方法返回当前对象。
您可以在实现IEnumerable
上迭代的.Net中的任何内容。如果您正在构建自己的类,并且它尚未从实现IEnumerable
的类继承,则可以通过实现foreach
(以及通过实现IEnumerable
来使您的类在GetEnumerator
语句中可用创建一个新的{{1}}方法将返回的枚举器类。
答案 1 :(得分:133)
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构造进行评估 说明一下,跟我来!
假设我们有一个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接口形式化,该接口位于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.
}
如果您想更新Garage类型以支持这些接口,您可以采取漫长的道路 手动实现每个方法。虽然您当然可以免费提供定制版本 GetEnumerator(),MoveNext(),Current和Reset(),有一种更简单的方法。由于System.Array类型(以及许多其他集合类)已经实现了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();
}
}
更新Garage类型后,可以安全地使用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的功能,只需make 使用显式接口实现:
IEnumerator IEnumerable.GetEnumerator()
{
// Return the array object's IEnumerator.
return carArray.GetEnumerator();
}
通过这样做,临时对象用户将找不到Garage的GetEnumerator()方法,而 foreach构造将在必要时在后台获取接口。
答案 2 :(得分:57)
实现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...
}
无论如何,那是不同的。
答案 3 :(得分:46)
首先是没有代码的解释,然后我会在以后添加它。
假设您正在经营一家航空公司。在每架飞机上,您都想知道飞机上乘客的信息。基本上你希望能够“遍历”飞机。换句话说,你希望能够从前座开始,然后沿着飞机后方前进,向乘客询问一些信息:他们是谁,他们来自哪里等。飞机只能这样做,如果是:
为什么要满足这些要求?因为这就是界面所需要的。
如果这是信息过载,您需要知道的是,您希望能够向飞机上的每位乘客询问一些问题,从第一个开始,直到最后一个。
可数意味着什么?
如果一家航空公司“可数”,这意味着飞机上必须有一名乘务员,他的唯一工作就是计算 - 而这名乘务员必须以非常具体的方式计算:
计数程序
航空公司的船长希望在每位乘客被调查或计算时对其进行报告。因此,在与第一个座位上的人员交谈后,乘务员/柜台然后向船长报告,当报告发出时,柜台会记住他/她在过道中的确切位置并继续计算他/她离开的地方关闭。
通过这种方式,船长始终可以获得有关当前被调查人员的信息。这样,如果他发现这个人喜欢曼城,那么他可以给予该乘客优惠待遇等。
让我们将其与IEnumerables
联系起来可枚举只是飞机上的乘客集合。民航法 - 这些基本上是所有IEnumerables必须遵循的规则。每当航空公司服务员带着乘客信息前往船长时,我们基本上都会“让”乘客到船长。机长基本上可以随意为乘客做任何事情 - 除了重新安排飞机上的乘客。在这种情况下,如果他们跟随曼城(呃!),他们将获得优惠待遇
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)
<强>摘要强>
换句话说,如果有一个计数器,某些东西是可数的。计数器必须(基本上):( i)记住它的位置(状态),(ii)能够移动,(iii)并了解他正在处理的当前人。
可数只是“可数”的一个奇特的词。换句话说,可枚举允许您“枚举”(即计数)。
答案 4 :(得分:22)
IEnumerable实现了GetEnumerator。调用时,该方法将返回IEnumerator,它实现MoveNext,Reset和Current。
因此,当你的类实现IEnumerable时,你说你可以调用一个方法(GetEnumerator)并获得一个新的对象返回(一个IEnumerator),你可以在一个循环中使用它,比如foreach。
答案 5 :(得分:17)
实现IEnumerable使您可以获取列表的IEnumerator。
IEnumerator允许使用yield关键字对列表中的项进行foreach样式顺序访问。
在foreach实现之前(例如在Java 1.4中),迭代列表的方法是从列表中获取一个枚举器,然后询问列表中的“next”项,只要返回的值因为下一个项目不是null。 Foreach只是隐式地将其作为一种语言特征,就像lock()在幕后实现Monitor类一样。
我希望foreach可以在列表上运行,因为它们实现了IEnumerable。
答案 6 :(得分:15)
将可枚举对象视为列表,堆栈,树。
答案 7 :(得分:11)
IEnumerable和IEnumerator(及其通用对应物IEnumerable&lt; T&gt;和IEnumerator&lt; T&gt;)是iterator中.Net Framework Class Libray collections实现的基接口。
IEnumerable 是您在大多数代码中看到的最常见的界面。它支持foreach循环,生成器(想想 yield ),并且由于其微小的界面,它用于创建紧密的抽象。 IEnumerable depends on IEnumerator
另一方面,IEnumerator 提供稍低级别的迭代接口。它被称为explicit iterator,它使程序员能够更好地控制迭代周期。
IEnumerable是一个标准接口,可以迭代支持它的集合(实际上,我今天能想到的所有集合类型都实现了 IEnumerable )。编译器支持允许使用foreach
等语言功能。一般而言,它启用此implicit iterator implementation。
foreach (var value in list)
Console.WriteLine(value);
我认为foreach
循环是使用 IEnumerable 接口的主要原因之一。与经典的 C 样式for循环相比,foreach
具有非常简洁的语法并且非常容易理解,您需要检查各种变量以查看它在做什么。
可能一个鲜为人知的功能是 IEnumerable 还可以使用yield return
和yield break
语句启用generators in C#。
IEnumerable<Thing> GetThings() {
if (isNotReady) yield break;
while (thereIsMore)
yield return GetOneMoreThing();
}
实践中的另一个常见场景是使用 IEnumerable 来提供简约抽象。由于它是一个微小且只读的界面,因此建议您将集合公开为 IEnumerable (而不是 List )。这样您就可以自由地更改您的实现而不会破坏客户端的代码(例如,将List更改为 LinkedList )。
需要注意的一个行为是,在流实现中(例如,从数据库逐行检索数据,而不是先将所有结果加载到内存中),不能迭代遍历集合一旦。这与像 List 这样的内存中集合形成对比,在这些集合中,您可以多次迭代而不会出现问题。例如,ReSharper,has a code inspection for Possible multiple enumeration of IEnumerable。
var iter = list.GetEnumerator();
while (iter.MoveNext())
Console.WriteLine(iter.Current);
根据我的经验, IEnumerator 在常见场景中很少使用,因为它的语法更加冗长,语义略有混乱(至少对我而言;例如 MoveNext()返回一个值同样,这个名字根本没有提示。
我只使用了 IEnumerator 特别(稍低级别)的库和框架,我提供了 IEnumerable 接口。一个例子是数据流处理库,它在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;
}
}
答案 8 :(得分:8)
实现IEnumerable
实质上意味着可以迭代对象。这并不一定意味着它是一个数组,因为某些列表无法编入索引,但您可以枚举它们。
IEnumerator
是用于执行迭代的实际对象。它控制从列表中的一个对象移动到下一个对象。
大部分时间,IEnumerable
&amp; IEnumerator
透明地用作foreach
循环的一部分。
答案 9 :(得分:5)
IEnumerable和IEnumerator之间的差异:
每当我们将IEnumerable集合传递给另一个函数时,它 不知道项目/对象的当前位置(不知道它正在执行哪个项目)
IEnumerable有一个方法GetEnumerator()
public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }
IEnumerator有一个属性当前和两个方法Reset和MoveNext(这对于了解列表中项目的当前位置很有用)。
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
答案 10 :(得分:4)
了解Iterator模式对您有所帮助。我建议阅读相同内容。
在较高级别,迭代器模式可用于提供迭代任何类型集合的标准方法。 我们在迭代器模式,实际集合(客户端),聚合器和迭代器中有3个参与者。聚合是一个接口/抽象类,它有一个返回迭代器的方法。 Iterator是一个接口/抽象类,它具有允许我们遍历集合的方法。
为了实现模式,我们首先需要实现一个迭代器来生成一个可以迭代相关集合的具体集合(客户端) 然后集合(客户端)实现聚合器以返回上述迭代器的实例。
所以基本上在c#中,IEnumerable是抽象聚合,IEnumerator是抽象迭代器。 IEnumerable有一个方法GetEnumerator,负责创建所需类型的IEnumerator实例。像Lists这样的集合实现了IEnumerable。
实施例。
让我们假设我们有一个方法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++;
}
}
如果您有任何疑问,请随时提出。
答案 11 :(得分:3)
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; }
}
现在,我们将定义类,该类将充当我们类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();
}
}
答案 12 :(得分:2)
次要贡献。
他们中的许多人解释了何时使用&#39;并且&#39;与foreach&#39;一起使用。 我想在这里添加另一个状态差异,这是关于IEnumerable和IEnumerator之间的区别。
我根据以下讨论主题创建了以下代码示例。
IEnumerable , IEnumerator vs foreach, when to use what What is the difference between IEnumerator and IEnumerable?
枚举器保留函数调用之间的状态(迭代位置),而另一方面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());
}
}
答案 13 :(得分:2)
我注意到了这些差异:
一个。我们以不同的方式迭代列表,foreach可用于IEnumerable和while循环用于IEnumerator。
B中。当我们从一个方法传递到另一个方法时,IEnumerator可以记住当前索引(它开始使用当前索引)但是IEnumerable不能记住索引并且它将索引重置为开始。更多视频https://www.youtube.com/watch?v=jd3yUjGc9M0
答案 14 :(得分:1)
IEnumerable
和IEnumerator
都是C#中的接口。
IEnumerable
是定义单个方法GetEnumerator()
的接口,该方法返回一个IEnumerator
接口。
这用于对实现IEnumerable
可以与foreach
语句一起使用的集合的只读访问。
IEnumerator
有两种方法,MoveNext
和Reset
。它还具有一个名为Current
的属性。
下面显示了IEnumerable和IEnumerator的实现。
答案 15 :(得分:0)
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
*/