314

IEnumerable任何人都可以向我解释IEnumerator吗?

例如,何时在 foreach 上使用它?IEnumerable和有什么区别IEnumerator?为什么我们需要使用它?

4

16 回答 16

310

例如,何时在 foreach 上使用它?

你不使用IEnumerable"over" foreach。实施IEnumerable使使用成为foreach 可能

当您编写如下代码时:

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

它在功能上等同于编写:

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

通过“功能等效”,我的意思是这实际上就是编译器将代码变成的内容。你不能在这个例子中使用foreachon除非implements 。 baz bazIEnumerable

IEnumerable表示baz实现该方法

IEnumerator GetEnumerator()

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

bool MoveNext()

Object Current()

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

.Net 中可以迭代的任何内容 implements IEnumerable。如果您正在构建自己的类,并且它还没有从实现的类继承,则可以通过实现(并通过创建其新方法将返回的枚举器类)使您的类在语句中IEnumerable可用。foreachIEnumerableGetEnumerator

于 2009-02-17T19:41:33.280 回答
185

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

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

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

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

于 2014-08-31T00:29:04.243 回答
68

实现 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...
}

无论如何,这就是区别。

于 2009-02-17T19:16:14.857 回答
61

通过类比解释 + 代码演练

类比:你是飞机上的中央情报局特工。您想知道有关乘客的信息,即您需要“遍历”它以便逮捕您的嫌疑人:

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

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

  1. 可数的,并且
  2. 如果它有一个柜台。

为什么有这些要求?因为这就是接口所需要的。

(如果这是信息过载,您只需要知道您希望能够向每位乘客提出一些问题)

可数是什么意思?

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

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

计数程序

航空公司的机长希望在对每位乘客进行调查时得到一份报告。因此,在与第一个座位上的人交谈后,乘务员/柜台然后向机长报告,报告完成后,柜台会记住他/她在过道的确切位置,并从他/她离开的地方继续计数离开。

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

  • 柜台一直走,直到他到达飞机的尽头。

让我们将其与 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)
    

概括

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

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

于 2016-10-25T20:46:44.513 回答
24

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

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

于 2009-02-17T19:19:16.143 回答
18

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

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

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

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

于 2009-02-17T19:12:48.640 回答
16
  • 实现IEnumerable的对象允许其他人访问它的每个项目(通过枚举器)
  • 实现IEnumerator的对象正在执行迭代。它在一个可枚举的对象上循环。

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

于 2009-02-17T19:17:05.463 回答
15

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

IEnumerable是您在大多数代码中看到的最常见的接口。它启用了 foreach 循环、生成器(想想yield),并且由于它的接口很小,它被用来创建紧密的抽象。IEnumerable 取决于 IEnumerator

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

IEnumerable

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

foreach 循环

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

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

产量关键字

可能一个鲜为人知的特性是IEnumerable还可以在 C#中使用yield returnandyield break语句启用生成器。

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

抽象

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

明白了

需要注意的一种行为是,在流实现中(例如,从数据库中逐行检索数据,而不是先将所有结果加载到内存中),您不能对集合进行多次迭代。这与诸如List之类的内存集合形成对比,您可以在其中迭代多次而不会出现问题。例如,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;   
    }
}
于 2017-06-03T01:46:41.480 回答
11

IEnumerable 和 IEnumerator 之间的区别:

  • IEnumerable 在内部使用 IEnumerator。
  • 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();
}
于 2015-06-18T12:06:11.763 回答
10

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

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

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

于 2009-02-17T19:19:00.043 回答
7

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

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

        }
    }
于 2019-05-04T22:59:14.140 回答
4

了解迭代器模式将对您有所帮助。我建议阅读相同的内容。

迭代器模式

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

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

这是UML图 迭代器模式

所以基本上在 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++;
            }
        }

如果您有任何问题,请随时提问。

于 2016-10-20T15:56:56.907 回答
2

一个小的贡献。

正如他们中的许多人解释“何时使用”和“与 foreach 一起使用”。我想在这里添加另一个州差异,作为关于 IEnumerable 和 IEnumerator 之间差异的问题。

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

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

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());
    }
}
于 2014-04-28T18:19:10.367 回答
2

我注意到了这些差异:

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

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

于 2015-07-27T08:53:10.780 回答
2

IEnumerable两者IEnumerator都是 C# 中的接口。

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

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

IEnumerator有两种方法,MoveNextReset。它还有一个名为Current.

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

于 2019-04-12T23:52:59.543 回答
1
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
  */
于 2017-11-02T18:28:37.523 回答