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

问题描述 投票:234回答:16

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

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

c# ienumerable ienumerator
16个回答
245
投票

例如,什么时候在foreach上使用它?

你不要使用IEnumerable“over”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,第二种方法返回当前对象。

你可以迭代的.Net中的任何东西都可以实现IEnumerable。如果您正在构建自己的类,并且它尚未从实现IEnumerable的类继承,则可以通过实现foreach(并通过创建其新的IEnumerable方法将返回的枚举器类)使您的类在GetEnumerator语句中可用。


5
投票

IEnumerable和IEnumerator之间的差异:

  • IEnumerable在内部使用IEnumerator。
  • IEnumerable不知道正在执行哪个项目/对象。
  • 每当我们将IEnumerator传递给另一个函数时,它就知道item / object的当前位置。
  • 每当我们将IEnumerable集合传递给另一个函数时,它就不知道item / object的当前位置(不知道它正在执行哪个项目) IEnumerable有一个方法GetEnumerator()
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}

IEnumerator有一个Property current和两个方法Reset和MoveNext(这对于了解列表中项目的当前位置很有用)。

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

4
投票

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

Iterator Pattern

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

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

这是UML图Iterator Pattern

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

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


3
投票

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

现在我们将定义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();

        }
    }

2
投票

次要贡献。

正如他们中的许多人解释“何时使用”和“与foreach一起使用”。我想在这里添加另一个状态差异,这是关于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());
    }
}

2
投票

我注意到了这些差异:

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

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


1
投票

IEnumerableIEnumerator都是C#中的接口。

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

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

IEnumerator有两种方法,MoveNextReset。它还有一个叫做Current的房产。

以下显示了IEnumerable和IEnumerator的实现。


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

138
投票

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 and the .NET 4.5 Framework


58
投票

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

无论如何,这是不同的。


46
投票

通过类比+代码演练解释

首先是没有代码的解释,然后我会在以后添加它。

假设您正在经营一家航空公司。在每架飞机上,您都想知道飞机上乘客的信息。基本上你希望能够“遍历”飞机。换句话说,你希望能够从前座开始,然后沿着飞机后方前进,向乘客询问一些信息:他们是谁,他们来自哪里等。飞机只能这样做, 如果是:

  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)

摘要

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

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


22
投票

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

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


17
投票

实现IEnumerable使您可以获取列表的IEnumerator。

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

在foreach实现之前(例如在Java 1.4中),迭代列表的方法是从列表中获取一个枚举器,然后询问列表中的“next”项,只要该值返回为下一个item不为null。 Foreach只是隐式地将其作为语言特征,就像lock()在幕后实现Monitor类一样。

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


15
投票
  • 实现IEnumerable的对象允许其他人访问其每个项目(通过枚举器)。
  • 实现IEnumerator的对象是进行迭代。它循环遍历一个可枚举的对象。

考虑列表,堆栈,树中的可枚举对象。


11
投票

IEnumerable和IEnumerator(以及它们的通用对应物IEnumerable <T>和IEnumerator <T>)是iterator.Net Framework Class Libray collections实现的基本接口。

IEnumerable是你在大多数代码中看到的最常见的接口。它支持foreach循环,生成器(想想产量),并且由于其微小的界面,它用于创建紧密的抽象。 IEnumerable depends on IEnumerator

另一方面,IEnumerator提供稍低级别的迭代接口。它被称为explicit iterator,它使程序员能够更好地控制迭代周期。

IEnumerable

IEnumerable是一个标准接口,可以迭代支持它的集合(实际上,我今天能想到的所有集合类型都实现了IEnumerable)。编译器支持允许使用foreach等语言功能。一般来说,它使这个implicit iterator implementation

foreach Loop

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

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

yield Keyword

可能一个鲜为人知的特征是IEnumerable也可以使用generators in C#yield return语句来启用yield break

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

Abstractions

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

Gotcha

需要注意的一个行为是,在流式实现中(例如,从数据库逐行检索数据,而不是首先将所有结果加载到内存中),您不能多次迭代该集合。这与像List这样的内存中集合形成对比,在这些集合中,您可以多次迭代而不会出现问题。例如,ReSharper,has a code inspection for Possible multiple enumeration of IEnumerable

IEnumerator

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

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

根据我的经验,IEnumerator在常见场景中很少使用,因为它的语法更冗长,语义略有混乱(至少对我而言;例如,MoveNext()也返回一个值,该名称根本没有提示)。

Use case for IEnumerator

我只使用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
投票

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.