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

例如,什么时候用它胜过foreach?IEnumerable和IEnumerator的区别是什么?为什么我们需要使用它?


当前回答

小贡献。

正如他们中的许多人解释“何时使用”和“与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());
    }
}

其他回答

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

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接口形式化,该接口隐藏在系统中。集合名称空间。 支持此行为的类或结构宣称它们能够公开所包含的内容 调用者的子项(在本例中为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框架

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

类比:假设你是一架飞机上的侦探。你得在所有乘客中找到嫌疑人。

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

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

可数是什么意思?

如果一家航空公司是可数的,这意味着飞机上必须有一名空乘人员,他的唯一工作就是数数:

柜台/空乘人员必须先于第一位乘客开始工作。(即空乘人员)必须“下一个移动”到过道的第一个座位。 然后他/她要记录:(i)坐在座位上的人,以及(ii)他们目前在过道上的位置。

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

让我们把它和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)了解他正在处理的当前对象。

可枚举的只是“可数的”的一个花哨的词。

小贡献。

正如他们中的许多人解释“何时使用”和“与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());
    }
}

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

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

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