Sunday, 19 February 2017

IEnumerable & IEnumerator

The IEnuerable<T> interface exposes an enumerator for generic collections, it facilitates the ability for you to use a foreach loop on your collection. so first to demonstrate this let's create a simple object we'll be collecting; as always a "Person" will do the trick.

class Person {
    public string FirstName { get; set; }
    public string  LastName { get; set; }
    public override string ToString() { return $"{FirstName} {LastName}"; }
}

once we have our person defined, we are going to need a custom non-generic collection, we'll call our People, because that's what you would call a group of Persons. We'll just start with the People class we built in the indexer post.

class People
{
    Person[] _ppl;
    public People(params Person[] ppl) _ppl = ppl; }

    public Person this[int Index]
    {
        get { return _ppl[Index]; }
        set { _ppl[Index] = value; }
    }

    public int Length { get { return _ppl == null ? 0 : _ppl.Length; } }

}

We see that we have a very simple People class the exposes a collection of persons, it has an indexer so that we can reference the contents by index and we expose a length property that will facilitate us iterating over our custom collection with a for loop. However what if we want to access our collection using the for each loop? well in that case we have to implement the IEnumerable<T> interface on our collection which will have to use an enumerator that has to implement the IEnumerator<T> interface.

Let's get started by checking out the IEnumerable<T> Interface.

using System.Runtime.CompilerServices;

namespace System.Collections.Generic
{

// Summary:
//     Exposes the enumerator, which supports a simple iteration over a collection of
//     a specified type.

// Type parameters:
//   T:
//     The type of objects to enumerate.This type parameter is covariant. That is, you
//     can use either the type you specified or any type that is more derived. For more
//     information about covariance and contravariance, see Covariance and Contravariance
//     in Generics.
    [TypeDependencyAttribute("System.SZArrayHelper")]
    public interface IEnumerable<out T> : IEnumerable
    {

// Summary:
//     Returns an enumerator that iterates through the collection.

// Returns:
//     A System.Collections.Generic.IEnumerator`1 that can be used to iterate through
//     the collection.
        IEnumerator<T> GetEnumerator();
    }
}

It just requires you to implement one function that returns an IEnumerator<T>, and as you can see it inherits from the non-generic IEnumerable interface, meaning that we'll have to also implement any members defined in that base interface, so let's go ahead and implement the IEnumerable<T> interface on our People Collection.

class People : IEnumerable<Person>
{
    Person[] _ppl;
    public People(params Person[] ppl) { _ppl = ppl; }

    public Person this[int Index]
    {
        get { return _ppl[Index]; }
        set { _ppl[Index] = value; }
    }

    public int Length { get { return _ppl == null ? 0 : _ppl.Length; } }
    public IEnumerator<Person> GetEnumerator() { throw new NotImplementedException(); }
    IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); }
}

not a drastic change however we do need to create a PeopleEnumerator class that implements IEnumerator<T> so that our public GetEnumerator() function has something to return. so let's get started by first checking out the IEnumerator<T> interface definition.

namespace System.Collections.Generic
{
// Summary:
//     Supports a simple iteration over a generic collection.

// Type parameters:
//   T:
//     The type of objects to enumerate.This type parameter is covariant. That is, you
//     can use either the type you specified or any type that is more derived. For more
//     information about covariance and contravariance, see Covariance and Contravariance
//     in Generics.
    public interface IEnumerator<out T> : IDisposable, IEnumerator
    {
// Summary:
//     Gets the element in the collection at the current position of the enumerator.
//
// Returns:
//     The element in the collection at the current position of the enumerator.
        T Current { get; }
    }
}

a bit more complicated, but by now means rocket science, we have a generic Current property, however as you can see our interface inherits from IDisposable, and IEnumerator which again means we'll have to implement the members in those Interfaces:

  • IEnumerator<T>
    • Current: which returns the current item as its type.
  • IEnumerator
    • Current: wich return the current item as an object. (from IEnumerator)
    • MoveNext: which returns true if there's another element and false if current is at the last
    • Reset: which just sets the enumerator to the initial position.
  • IDisposable
    • Dispose: cleans up and disposes and unsafe pointers and removes any attached events or delegates.

so let's go ahead and implement our IEnumerator<T> on our PeopleEnumerator.

class PeopleEnumerator : IEnumerator<Person>
{
    Person[] _nodes;
    int _currentIndex;

    public PeopleEnumerator(Person[] People) 
        _nodes = People; 
        Reset(); 
    }

    public Person Current get return _nodes[_currentIndex]; }

    object IEnumerator.Current get return Current; }

    public void Dispose() //throw new NotImplementedException(); }

    public bool MoveNext() return (++_currentIndex < _nodes.Length); }

    public void Reset() _currentIndex = -1; }

}

now this is where the meat is, let's get started with the first two fields we declare,

Person[] _nodes;
int _currentIndex;

 _nodes is just an array of where we are going to keep all of our elements and currentindex is the index of the current node we are going to output, so 0 for the first node, 1 for the second, 2 for the third etc; basically our place holder.

Next we have our constructor

public PeopleEnumerator(Person[] People) _nodes = People; Reset(); }

all it does is take in our array of people and sets it to our _nodes array, letting us iterate over our collection of people, and then it calls our Reset method.

the reset method

public void Reset() _currentIndex = -1; }

simple set's our _currentIndex to -1 the reason it's -1 and not zero is the first thing our foreach loop does is checks if our collection can move to the next node

public bool MoveNext() return (++_currentIndex < _nodes.Length); }

and as you can see the first thing that the MoveNext() function does is increment our current index field by 1, so if it's -1 the first one it'll check is 0

We have our Current property that has a return type of Person

public Person Current get return _nodes[_currentIndex]; }

all it does is uses the current index with our collection of nodes to return the current Person. It's followed by the explicit implementation that comes form the non-generic IEnuerator interface of current which returns an object.

object IEnumerator.Current get return Current; }

because of polymorphism we can just return the value of Current as a person and it'll get cast to an object

and finally we have the dispose method which is called every time a foreach loop is completed,

public void Dispose() //throw new NotImplementedException(); }

so in our case since we don't need to dispose anything we just comment out the not implemented exception.

Now let's take a look at our implementation

class Program
{
    static void Main(string[] args)
    {
        var p1 = new Person { FirstName = "Pawel", LastName = "Ciucias" };
        var p2 = new Person { FirstName = "Tomek", LastName = "Ciucias" };
        var p3 = new Person { FirstName = "Jakub", LastName = "Tywoniuk" };
        var p4 = new Person { FirstName = "Magda", LastName = "Tywoniuk" };

        var ppl = new People(p1, p2, p3, p4);

        for (var i = 0; i < ppl.Length; i++)
            Console.WriteLine(ppl[i].ToString());

        foreach(var p in ppl)
            Console.WriteLine(p.ToString());
    }

}

now we can iterate over all the elements in our collection using either a for or foreach loop. the following is what happens when you try to use a foreach loop

  • Our people class returns a new instance of the PeopleEnumerator, which fires our constructor setting our nodes and reseting our current index.
  • next our PeopleEnumerator's Move next function fires, which first increments our current index by one then returns whether our current index is less than the number of elements we have
    • if yes it returns true, the current element and increments again
    • if no it returns false, stops and calls the dispose method.