Sunday, 13 August 2017

The IEnumerable and IEnumerator Interfaces

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



--------------------------------------------------------




Introduction

First let's see a small demonstration of both of these interfaces and then we will talk about the differences.

DEMO

Create a new console application and in the main method add the following.
  1. static void Main(string[] args)
  2. {  
  3.    List<string> Month = new List<string>();  
  4.    Month.Add("January");  
  5.    Month.Add("February");  
  6.    Month.Add("March");  
  7.    Month.Add("April");  
  8.    Month.Add("May");  
  9.    Month.Add("June");  
  10.    Month.Add("July");  
  11.    Month.Add("August");  
  12.    Month.Add("September");  
  13.    Month.Add("October");  
  14.    Month.Add("November");  
  15.    Month.Add("December");  
  16. }  
In our main method, we created a new list collection “Month” of type string. In this collection we added all the 12 months as items in this list.

Note: The List class is present in the System.Collections.Generic namespace.

Now let's create a very simple IEnumerable on this list.
  1. //create IEnumerable of string  
  2. IEnumerable<string> iEnumerableOfString = (IEnumerable<string>)Month;  
  3.   
  4. //If we want to retrieve all the items from this IEnumerable object, we can use a foreach loop. 

  5. foreach(string AllMonths in iEnumerableOfString)
  6. {  
  7.    Console.WriteLine(AllMonths);  
  8. }  
Run the application.


We got the list of months.

Now let's see how to do the same, using an IEnumerator and then we will see the differences.
  1. //Create IEnumerator of string.
     
     
  2. IEnumerator<string> iEnumeratorOfString = Month.GetEnumerator();//to convert list into IEnumerator we can invoke the GetEnumerator method  
  3.   
  4. //To retrieve all the items from the above IEnumerator object, we cannot use foreach loop instead of that we need to invoke MoveNext() Boolean method.  
  5. while(iEnumeratorOfString.MoveNext()) 
  6. {  
  7.   
  8. }  
To display the items on the console window we need to invoke the Current property as in the following:
  1. while(iEnumeratorOfString.MoveNext())  
  2. {  
  3.    Console.WriteLine(iEnumeratorOfString.Current);  
  4. }  
Run the application.



Similarities

Both of these interfaces help to loop through the collection.

So, now the next question is, what should I use?

As we know, both of these interfaces give the same result. But if you watch the syntax for IEnumerable, it is very simple.
  1. foreach(string AllMonths in iEnumerableOfString)  
  2. {  
  3.    Console.WriteLine(AllMonths);  
  4. }  
But in the case of IEnumerator, we need to invoke the MoveNext method and to retrieve the current item, we need to invoke the current property.

Relation
The IEnumerable interface actually uses IEnumerator. The main reason to create an IEnumerable is to make the syntax shorter and simpler.

If you go to the definition of the IEnumerable<T> interface, you will see this interface has a method GetEnumerator() that returns an IEnumerator object back.


In short, this IEnumerable uses IEnumerator internally.

Differences
The main difference between IEnumerable and IEnumerator is an IEnumerator retains its cursor's current state.

Let's understand this practically.

Create two static methods in the main program.
  1. static void iEnumeratorMethodOne(IEnumerator<string> i)  
  2. {  
  3.    while(i.MoveNext())  
  4.    {  
  5.       Console.WriteLine(i.Current);  
  6.   
  7.        if(i.Current == "June")  
  8.        {  
  9.           iEnumeratorMethodTwo(i);  
  10.        }  
  11.     }  
  12. }  
  13.   
  14. static void iEnumeratorMethodTwo(IEnumerator<string> i)  
  15. {  
  16.     while(i.MoveNext())  
  17.     {  
  18.        Console.WriteLine(i.Current);  
  19.      }  
  20. }  
Explanation
  1. In the iEnumeratorMethodOne method, we added a parameter “i” of type IEnumerator of string.
  2. Inside the method block, we used a while loop to move to the next item using the MoveNext method and if the item is found then we invoked the Current property that will provide us the current item and we are displaying that item on the console window.
  3. In the if block we are checking the current item state, if the item is “June” then we invoke the second iEnumerator method and in that method we passed the IEnumerable of string object. In other words, i.
    Then invoke this method in the main method and pass an iEnumeratorOfString as a parameter argument.
  1. IEnumerator<string> iEnumeratorOfString = Month.GetEnumerator();  
  2. iEnumerableMethodOne(iEnumeratorOfString);  
Put a breakpoint on both the iEnumeratorMethods and press F10.

Look at the current item; it is January.
In the if block, the current item is being compared with the item “June”. Once the current item is June then it will invoke the second method.

Press F10 until the current item reaches June.



So, now the current item is June which means the if block will be executed and this block will invoke iEnumeratorMethoTwo.


Look at the current item present in the i object, “June”.

Once this iEnumeratorMethoTwo executes, look at the current item.


It's July.



Then August and so on.

So, IEnumerator retains its cursor state.

In the end we will get the following result.



So, if you want to loop sequentially through the collection, use an IEnumerable interface else if you want to retain the cursor position and want to pass it from one function to another function then use an IEnumerator interface.







No comments:

Post a Comment