Closing the Gap Between IEnumerator and IEnumerable

From eqqon

Revision as of 09:53, 21 April 2008 by Henon (Talk | contribs)
Jump to: navigation, search

Ever found yourself writing a custom collection in C# just to be able to fill in an IEnumerator into an interface that expects IEnumerable? Boilerplate code like such is never a good idea. The class Iterator<T> presented in this article fills the missing link between IEnumerator and IEnumerable.

Contents

Introduction

C# defines two interfaces for convenient iteration over a sequence of values or objects. IEnumerable which allows to "foreach" over a collection and is implemented basically by every collection of the .NET framework and IEnumerator which is exposed by an iterator which is the return value of an iteration function. Both interfaces exist in generic and non-generic versions but for simplicity we only talk about the non-generic versions without loss of generality. The definition of IEnumerable is as simple as this:

IEnumerable

interface IEnumerable
{
IEnumerator GetEnumerator();
}

The main purpose of this interface is to allow a syntactically uncomplicated and hence convenient way to iterate over a collection by using the foreach statement. This convenience and the fact, that it is exposed by every standard collection makes IEnumerable the perfect parameter type for methods that accept any kind of collection. A prominent example use case is LINQ which allows to pipeline IEnumerables through a number of cascaded transformation functions.

Strictly speaking IEnumerable is just a wrapper for IEnumerator which enables foreach magic for any type which implements it. So let's have a look at IEnumerator:

interface IEnumerator

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

Obviously IEnumerator is the typical interface of an iterator. To create a custom iterator (an object exposing interface IEnumerator) there are two major approaches. Your class can "manually" implement the IEnumerator interface. The second and much more interesting one is to use another magic syntax of C# which is called iterator block:

Example iterator block which generates the first 7 elements of the Fibonacci sequence
IEnumerator SimpleIterator()
{
yield return 1;
yield return 1;
yield return 2;
yield return 3;
yield return 5;
yield return 8;
yield return 13;
}
Here is an iterator block which generates a Fibonacci sequence of arbitrary length
IEnumerator<int> Fibonacci(int count)
{
int n0=0;
int n1=1;
for(int i=0; i<count; i++)
{
yield return n1;
int next = n0 + n1;
n0 = n1;
n1 = next;
}
}

The iterator block is a simple method, indexer or property getter which returns IEnumerator or IEnumerator<T>. This is an incredibly useful and even more convenient way to write custom iterators. Moreover iterator blocks are evaluated on demand only so they are the most efficient way to handle large sequences, transformation pipelines or sequences which are created by a mathematical function without consuming memory unnecessarily. If one chains together a number of iterators each iteration block pulls objects from the next iterator for each iteration on demand.

Chaining Iterators to form Processing Pipelines

Example of chaining iteration blocks
IEnumerator<int> FibonacciSquared(int count)
{
var inner_iterator = Fibonacci(count);
while(inner_iterator.MoveNext())
{
int n = inner_iterator.Current;
yield return n * n;
}
}

For the sake of improved readability I prefer to use the foreach statement over manually incrementing the iterator via the IEnumerator interface but this is not directly supported by C#. Writing a custom collection which implements IEnumerable to wrap the iterator block just to be able to foreach over the iterator block is nonsense. Besides some classes may expose several iterators which support different ways of traversal (i.e. bottom-up vs. top-down iteration over a tree). Also if one wants to use a custom iterator with LINQ the conversion from IEnumerator to IEnumerable is necessary. The solution is simple and elegant:

Example of foreach-ing over an iteration block using our featured class Iterator<T>
IEnumerator<int> FibonacciSquared(int count)
{
foreach( int n in new Iterator<int>( Fibonacci(count)))
{
yield return n*n;
}
}
Example of using LINQ with an iteration block called Traverse which enumerates all nodes in a tree.

var nodes = new Iterator( tree.Traverse()); // <--- Traverse is an iteration block and returns an IEnumerator which cannot directly be fed into a LINQ query. var leaves = from n in nodes where n.IsLeaf() select n;

class Iterator

Iterator is a generalized wrapper to "convert" IEnumerator into IEnumerable.

public class Iterator : IEnumerable
{
    IEnumerator m_iterator;

    public Iterator(IEnumerator iter)
    {
        m_iterator = iter;
    }

    public Iterator(IEnumerable enumerable)
    {
        m_iterator = enumerable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return m_iterator;
    }
}

class Iterator<T>

The generic version of Iterator.

public class Iterator<T> : IEnumerable<T>
{
    IEnumerator<T> m_iterator;

    public Iterator(IEnumerator<T> iter)
    {
        m_iterator = iter;
    }

    public Iterator(IEnumerable<T> enumerable)
    {
        m_iterator = enumerable.GetEnumerator();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return m_iterator;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return (IEnumerator)m_iterator;
    }
}


--Henon 01:00, 21 April 2008 (CEST)