Can anyone explain IEnumerable and IEnumerator to me?

Viewed 275210

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?

16 Answers

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).

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.

Explanation via Analogy + Code Walkthrough

Analogy: you're an CIA agent on a plane. You want to know information about the passengers i.e. you need to "traverse" it so you can apprehend your suspect:

  • You start at the front of the plane, and then work your way to the back asking passengers: 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 some questions)

What does countable mean?

If an airline is "countable", this means that there MUST be a flight attendant present on the plane, whose 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. 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).

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.

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.

  • 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.

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.

IEnumerable is a box that contains Ienumerator. IEnumerable is base interface for all the collections. foreach loop can operate if the collection implements IEnumerable. In the below code it explains the step of having our own Enumerator. Lets first define our Class of which we are going to make the collection.

public class Customer
{
    public String Name { get; set; }
    public String City { get; set; }
    public long Mobile { get; set; }
    public double Amount { get; set; }
}

Now we will define the Class which will act as a collection for our class Customer. Notice that it is implementing the interface IEnumerable. So that we have to implement the method GetEnumerator. This will return our custom Enumerator.

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

Now we are going to create our own custom Enumerator as follow. So, we have to implement method 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.
        }
    }

Now we can use foreach loop over our collection like below;

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

        }
    }

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 Pattern

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.

IEnumerable and IEnumerator both are interfaces in C#.

IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface.

This works for read-only access to a collection that implements that IEnumerable can be used with a foreach statement.

IEnumerator has two methods, MoveNext and Reset. It also has a property called Current.

The following shows the implementation of IEnumerable and IEnumerator.

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