有人能向我解释一下 IEnumerable
和 IEnumerator
吗?
例如,什么时候应该使用它而不是 foreach?IEnumerable
和 IEnumerator
之间的区别是什么?为什么我们需要使用它?
有人能向我解释一下 IEnumerable
和 IEnumerator
吗?
例如,什么时候应该使用它而不是 foreach?IEnumerable
和 IEnumerator
之间的区别是什么?为什么我们需要使用它?
foreach (Foo bar in baz)
{
...
}
这在功能上等同于写作:
IEnumerator bat = baz.GetEnumerator();
while (bat.MoveNext())
{
Foo bar = (Foo)bat.Current;
...
}
IEnumerator GetEnumerator()
IEnumerator
对象必须实现这些方法。bool MoveNext()
并且
Object Current()
bool MoveNext()
方法和任意类型的 Current
属性。这种“鸭子类型”方法在 C# 1.0 中被实现,用于遍历值类型集合时避免装箱。 - phoogIEnumerable 和 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.
}
如果你想更新车库类型以支持这些接口,你可以走一条漫长的路,并手动实现每个方法。虽然你当然可以提供自定义版本的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();
}
}
在更新了你的车库类型之后,你可以在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();
}
这样做,普通的对象用户将找不到车库的GetEnumerator()方法,而foreach结构会在必要时在后台获取接口。
Garage
中创建一个获取carArray
的方法呢?这样您就不必自己实现GetEnumerator
,因为Array
已经实现了它。例如:foreach(Car c in carLot.getCars()) { ... }
- Nick Rolando实现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...
}
这就是区别。
类比:想象你是一名在飞机上的侦探。你需要穿过所有乘客,找到你的嫌疑人。
要做到这一点,飞机必须满足以下条件:
什么是“可枚举的”?
如果一个航空公司是“可枚举的”,这意味着必须有一名空中乘务员在飞机上,其唯一工作是计数:
计数器继续进行,直到达到飞机末端。
让我们将此与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)并了解它正在处理的当前人员。
Enumerable只是“可数”的花哨说法。
IEnumerable 实现了 GetEnumerator 方法。当调用此方法时,它将返回一个实现了 MoveNext、Reset 和 Current 方法的IEnumerator 对象。
因此,当您的类实现 IEnumerable 接口时,您就是在表示您可以调用一个方法(GetEnumerator),并返回一个新的对象(即 IEnumerator),您可以在诸如 foreach 循环之类的循环中使用该对象。
实现IEnumerable使您可以获取列表的IEnumerator。
IEnumerator允许使用yield关键字以foreach方式顺序访问列表中的项。
在foreach实现(例如在Java 1.4中)之前,迭代列表的方法是从列表获取枚举器,然后要求它返回列表中的“下一个”项,只要返回值作为下一个项不为null。 foreach只是将其隐式实现为语言特性,就像lock()在幕后实现Monitor类一样。
我期望foreach可以在列表上工作,因为它们实现了IEnumerable。
将可枚举对象视为列表、堆栈、树等。
IEnumerable和IEnumerator(以及它们的泛型对应物IEnumerable<T>和IEnumerator<T>)是迭代器在.Net Framework Class Libray collections中的基本接口。
IEnumerable是大多数代码中最常见的接口。它使得foreach循环、生成器(想一下yield)成为可能,由于其小巧的接口,它被用来创建紧凑的抽象层。IEnumerable依赖于IEnumerator。
另一方面,IEnumerator提供了稍微低级的迭代接口。它被称为显式迭代器,这使得程序员可以更好地控制迭代过程。
IEnumerable是一个标准接口,它使得可以遍历支持它的集合(事实上,我今天能想到的所有集合类型都实现了IEnumerable)。编译器支持语言特性,如foreach。一般来说,它使得隐式迭代器实现成为可能(具体请参见此处)。
foreach (var value in list)
Console.WriteLine(value);
我认为foreach
循环是使用IEnumerable接口的主要原因之一。与经典的C风格循环相比,foreach
具有非常简洁的语法,并且非常易于理解,后者需要检查各种变量以了解它正在做什么。
可能较少人知道的功能是,IEnumerable还可以通过使用yield return
和yield break
语句来启用C#中的生成器(generators)。
IEnumerable<Thing> GetThings() {
if (isNotReady) yield break;
while (thereIsMore)
yield return GetOneMoreThing();
}
在实践中另一个常见的场景是使用 IEnumerable 提供极简抽象。因为它是一个微小且只读的接口,鼓励您将您的集合公开为 IEnumerable(而不是例如 List)。这样你就可以自由地更改你的实现而不会破坏你的客户端代码(例如,将 List 更改为 LinkedList)。
需要注意的一种行为是,在流式实现中(例如从数据库逐行检索数据而不是先加载所有结果到内存中),您不能多次迭代集合。这与像 List 这样的内存中集合形成对比,后者可以多次迭代而不会出现问题。例如,ReSharper有一个用于检查 Possible multiple enumeration of IEnumerable 的代码检查。
另一方面,IEnumerator 是幕后接口,使得 IEnumerble-for-each-magic 起作用。严格来说,它支持显式迭代器。
var iter = list.GetEnumerator();
while (iter.MoveNext())
Console.WriteLine(iter.Current);
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;
}
}
IEnumerable和IEnumerator之间的区别:
每当我们将一个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();
}
实现 IEnumerable
接口意味着该对象可以被迭代。这并不一定意味着它是一个数组,因为某些列表不能被索引,但是你可以枚举它们。
IEnumerator
是用于执行迭代的实际对象。它控制从列表中的一个对象移动到下一个对象。
大多数情况下,IEnumerable
和 IEnumerator
透明地作为 foreach
循环的一部分使用。