Can anyone explain IEnumerable and IEnumerator to

2019-01-01 14:42发布

问题:

Can anyone explain IEnumerable and IEnumerator to me?

for example, when to use it over foreach? what\'s the difference between IEnumerable and IEnumerator? Why do we need to use it?

回答1:

for example, when to use it over foreach?

You don\'t use IEnumerable \"over\" foreach. Implementing IEnumerable makes using foreach possible.

When you write code like:

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

it\'s functionally equivalent to writing:

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

By \"functionally equivalent,\" I mean that\'s actually what the compiler turns the code into. You can\'t use foreach on baz in this example unless baz implements IEnumerable.

IEnumerable means that baz implements the method

IEnumerator GetEnumerator()

The IEnumerator object that this method returns must implement the methods

bool MoveNext()

and

Object Current()

The first method advances to the next object in the IEnumerable object that created the enumerator, returning false if it\'s done, and the second returns the current object.

Anything in .Net that you can iterate over implements IEnumerable. If you\'re building your own class, and it doesn\'t already inherit from a class that implements IEnumerable, you can make your class usable in foreach statements by implementing IEnumerable (and by creating an enumerator class that its new GetEnumerator method will return).



回答2:

The IEnumerable and IEnumerator Interfaces

To begin examining the process of implementing existing .NET interfaces, let’s first look at the role of IEnumerable and IEnumerator. Recall that C# supports a keyword named foreach that allows you to iterate over the contents of any array type:

// Iterate over an array of items.
int[] myArrayOfInts = {10, 20, 30, 40};
foreach(int i in myArrayOfInts)
{
   Console.WriteLine(i);
}

While it might seem that only array types can make use of this construct, the truth of the matter is any type supporting a method named GetEnumerator() can be evaluated by the foreach construct.To illustrate, follow me!

Suppose we have a Garage class:

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

Ideally, it would be convenient to iterate over the Garage object’s subitems using the foreach construct, just like an array of data values:

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

Sadly, the compiler informs you that the Garage class does not implement a method named GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking within the System.Collections namespace. Classes or structures that support this behavior advertise that they are able to expose contained subitems to the caller (in this example, the foreach keyword itself). Here is the definition of this standard .NET interface:

// This interface informs the caller
// that the object\'s subitems can be enumerated.
public interface IEnumerable
{
   IEnumerator GetEnumerator();
}

As you can see, the GetEnumerator() method returns a reference to yet another interface named System.Collections.IEnumerator. This interface provides the infrastructure to allow the caller to traverse the internal objects contained by the IEnumerable-compatible container:

// 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.
}

If you want to update the Garage type to support these interfaces, you could take the long road and implement each method manually. While you are certainly free to provide customized versions of GetEnumerator(), MoveNext(), Current, and Reset(), there is a simpler way. As the System.Array type (as well as many other collection classes) already implements IEnumerable and IEnumerator, you can simply delegate the request to the System.Array as follows:

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

After you have updated your Garage type, you can safely use the type within the C# foreach construct. Furthermore, given that the GetEnumerator() method has been defined publicly, the object user could also interact with the IEnumerator type:

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

However, if you prefer to hide the functionality of IEnumerable from the object level, simply make use of explicit interface implementation:

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

By doing so, the casual object user will not find the Garage’s GetEnumerator() method, while the foreach construct will obtain the interface in the background when necessary.

Adapted from the Pro C# 5.0 and the .NET 4.5 Framework



回答3:

Implementing IEnumerable means your class returns an IEnumerator object:

public class People : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator()
    {
        // return a PeopleEnumerator
    }
}

Implementing IEnumerator means your class returns the methods and properties for iteration:

public class PeopleEnumerator : IEnumerator
{
    public void Reset()...

    public bool MoveNext()...

    public object Current...
}

That\'s the difference anyway.



回答4:

Explanation via Analogy + Code Walkthrough

First an explanation without code, then I\'ll add it in later.

Let\'s say you are running an airline company. And in each plane you want to know information about the passengers flying in the plane. Basically you want to be able to \"traverse\" the plane. In other words, you want to be able to start at the front seat, and to then work your way towards the back of the plane, asking passengers some information: who they are, where they are from etc. An aeroplane can only do this, if it is:

  1. countable, and
  2. if it has a counter.

Why these requirements? Because that\'s what the interface requires.

If this is information overload, all you need to know is that you want to be able to ask each passenger in the plane some questions, starting from the first and making your way to the last.

What does countable mean?

If an airline is \"countable\", this means that there MUST be a flight attendant present on the plane, who\'s sole job is to count - and this flight attendant MUST count in a very specific manner:

  1. The counter/flight attendant MUST start before the first passenger (at the front of everyone where they demo safety, how to put the life jacket on etc).
  2. He/she (i.e. the flight attendant) MUST \"move next\" up the aisle to the first seat.
  3. He/she is to then record: (i) who the person is in the seat, and (ii) their current location in the aisle.

Counting Procedures

The captain of the airline wants a report on every passenger as and when they are investigated or counted. So after speaking to the person in the first seat, the flight-attendant/counter then reports to the captain, and when the report is given, the counter remembers his/her exact position in the aisle and continues counting right where he/she left off.

In this manner the captain is always able to have information on the current person being investigated. That way, if he finds out that this individual likes Manchester City, then he can give that passenger preferential treatment etc.

  • The counter keeps going till he reaches the end of the plane.

Let\'s tie this with the IEnumerables

  • An enumerable is just a collection of passengers on a plane. The Civil Aviation Law - these are basically the rules which all IEnumerables must follow. Everytime the airline attendant goes to the captain with the passeger information, we are basically \'yielding\' the passenger to the captain. The captain can basically do whatever he wants with the passenger - except rearranging the passengers on the plane. In this case, they are given preferential treatment if they follow Manchester City (ugh!)

    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)
    

Summary

In other words, something is countable if it has a counter. And counter must (basically): (i) remember its place (state), (ii) be able to move next, (iii) and know about the current person he is dealing with.

Enumerable is just a fancy word for \"countable\". In other words, an enumerable allows you to \'enumerate\' (i.e. count).



回答5:

IEnumerable implements GetEnumerator. When called, that method will return an IEnumerator which implements MoveNext, Reset and Current.

Thus when your class implements IEnumerable, you are saying that you can call a method (GetEnumerator) and get a new object returned (an IEnumerator) you can use in a loop such as foreach.



回答6:

Implementing IEnumerable enables you to get an IEnumerator for a list.

IEnumerator allows foreach style sequential access to the items in the list, using the yield keyword.

Before foreach implementation (in Java 1.4, for example), the way to iterate a list was to get an enumerator from the list, then ask it for the \"next\" item in the list, for as long as the value returned as the next item is not null. Foreach simply does that implicitly as a language feature, in the same way that lock() implements the Monitor class behind the scenes.

I expect foreach works on lists because they implement IEnumerable.



回答7:

  • An object implementing IEnumerable allows others to visit each of its items (by an enumerator).
  • An object implementing IEnumerator is the doing the iteration. It\'s looping over an enumerable object.

Think of enumerable objects as of lists, stacks, trees.



回答8:

IEnumerable and IEnumerator (and their generic counterparts IEnumerable<T> and IEnumerator<T>) are base interfaces of iterator implementations in .Net Framework Class Libray collections.

IEnumerable is the most common interface you would see in the majority of the code out there. It enables the foreach loop, generators (think yield) and because of its tiny interface, it\'s used to create tight abstractions. IEnumerable depends on IEnumerator.

IEnumerator, on the other hand, provides a slightly lower level iteration interface. It\'s referred to as the explicit iterator which gives the programmer more control over the iteration cycle.

IEnumerable

IEnumerable is a standard interface that enables iterating over collections that supports it (in fact, all collection types I can think of today implements IEnumerable). Compiler support allows language features like foreach. In general terms, it enables this implicit iterator implementation.

foreach Loop

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

I think foreach loop is one of the main reasons for using IEnumerable interfaces. foreach has a very succinct syntax and very easy to understand compared to classic C style for loops where you need to check the various variables to see what it was doing.

yield Keyword

Probably a lesser known feature is that IEnumerable also enables generators in C# with the use of yield return and yield break statements.

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

Abstractions

Another common scenario in practice is using IEnumerable to provide minimalistic abstractions. Because it is a minuscule and read-only interface, you are encouraged to expose your collections as IEnumerable (rather than List for example). That way you are free to change your implementation without breaking your client\'s code (change List to a LinkedList for instance).

Gotcha

One behaviour to be aware of is that in streaming implementations (e.g. retrieving data row by row from a database, instead of loading all the results in memory first) you cannot iterate over the collection more than once. This is in contrast to in-memory collections like List, where you can iterate multiple times without problems. ReSharper, for example, has a code inspection for Possible multiple enumeration of IEnumerable.

IEnumerator

IEnumerator, on the other hand, is the behind the scenes interface which makes IEnumerble-foreach-magic work. Strictly speaking, it enables explicit iterators.

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

In my experience IEnumerator is rarely used in common scenarios due to its more verbose syntax and slightly confusing semantics (at least to me; e.g. MoveNext() returns a value as well, which the name doesn\'t suggest at all).

Use case for IEnumerator

I only used IEnumerator in particular (slightly lower level) libraries and frameworks where I was providing IEnumerable interfaces. One example is a data stream processing library which provided series of objects in a foreach loop even though behind the scenes data was collected using various file streams and serialisations.

Client code

foreach(var item in feed.GetItems())
    Console.WriteLine(item);

Library

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


回答9:

Implementing IEnumerable essentially means that the object can be iterated over. This doesn\'t necessarily mean it is an array as there are certain lists that can\'t be indexed but you can enumerate them.

IEnumerator is the actual object used to perform the iterations. It controls moving from one object to the next in the list.

Most of the time, IEnumerable & IEnumerator are used transparently as part of a foreach loop.



回答10:

Differences between IEnumerable and IEnumerator :

  • IEnumerable uses IEnumerator internally.
  • IEnumerable doesnt know which item/object is executing.
  • Whenever we pass IEnumerator to another function ,it knows the current position of item/object.
  • Whenever we pass IEnumerable collection to another function ,it doesn\'t know the current position of item/object(doesn\'t know which item its executing)

    IEnumerable have one method GetEnumerator()

public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}

IEnumerator have one Property current and two methods Reset and MoveNext(Which is useful for knowing the current position of an item in a list).

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


回答11:

An understanding of the Iterator pattern will be helpful for you. I recommend reading the same.

Iterator Pattern

At a high level the iterator pattern can be used to provide a standard way of iterating through collections of any type. We have 3 participants in the iterator pattern, the actual collection (client), the aggregator and the iterator. The aggregate is an interface/abstract class that has a method that returns an iterator. Iterator is an interface/abstract class that has methods allowing us to iterate through a collection.

In order to implement the pattern we first need to implement an iterator to produce a concrete that can iterate over the concerned collection (client) Then the collection (client) implements the aggregator to return an instance of the above iterator.

Here is the UML diagram \"Iterator

So basically in c#, IEnumerable is the abstract aggregate and IEnumerator is the abstract Iterator. IEnumerable has a single method GetEnumerator that is responsible for creating an instance of IEnumerator of the desired type. Collections like Lists implement the IEnumerable.

Example. Lets suppose that we have a method getPermutations(inputString) that returns all the permutations of a string and that the method returns an instance of IEnumerable<string>

In order to count the number of permutations we could do something like the below.

 int count = 0;
        var permutations = perm.getPermutations(inputString);
        foreach (string permutation in permutations)
        {
            count++;
        }

The c# compiler more or less converts the above to

using (var permutationIterator = perm.getPermutations(input).GetEnumerator())
        {
            while (permutationIterator.MoveNext())
            {
                count++;
            }
        }

If you have any questions please don\'t hesitate to ask.



回答12:

A Minor contribution.

As many of them explain about \'when to use\' and \'use with foreach\'. I thought of adding Another States Difference here as requested in question about the difference between both IEnumerable an IEnumerator.

I created the below code sample based on the below discussion threads.

IEnumerable , IEnumerator vs foreach, when to use what What is the difference between IEnumerator and IEnumerable?

Enumerator preserves the state (iteration position) between function calls while iterations the other hand Enumerable does not.

Here is the tested example with comments to understand.

Experts please add/correct me.

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:

I have noticed these differences:

A. We iterate the list in different way, foreach can be used for IEnumerable and while loop for IEnumerator.

B. IEnumerator can remember the current index when we pass from one method to another (it start working with current index) but IEnumerable can\'t remember the index and it reset the index to beginning. More in this video https://www.youtube.com/watch?v=jd3yUjGc9M0



回答14:

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