Closing the Gap Between IEnumerator and IEnumerable

From eqqon

(Difference between revisions)
Jump to: navigation, search
m (interface IEnumerator)
 
(11 intermediate revisions not shown)
Line 1: Line 1:
-
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.
+
[[Category:CSharp]]
 +
== ==
 +
Ever found yourself writing a custom collection in C# just to be able to pass an IEnumerator into a method that expects IEnumerable? Writing such boilerplate code is never a good idea. The classes [[#class Iterator|Iterator]] and [[#class Iterator<T>|Iterator<T>]] presented in this article fill in the missing link from IEnumerator to IEnumerable allowing you to be more productive with iterators.
== Introduction ==
== 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:
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:
-
== interface IEnumerable ==
+
== IEnumerable ==
-
;interface IEnumerable
+
:interface IEnumerable  
-
:IEnumerator GetEnumerator();
+
:{
 +
::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.  
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.  
Line 12: Line 16:
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:
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 ==
+
== IEnumerator and Iteration Blocks ==
-
;interface IEnumerator
+
:interface IEnumerator
-
:void Reset();
+
:{
-
:bool MoveNext();
+
::void Reset();
-
:object Current{ get; }
+
::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''':
+
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 makes use of C# compiler magic and is called '''iterator block''':
;Example iterator block which generates the first 7 elements of the Fibonacci sequence
;Example iterator block which generates the first 7 elements of the Fibonacci sequence
Line 32: Line 38:
:}
:}
-
;Example iterator block which generates a Fibonacci sequence
+
;Here is an iterator block which generates a Fibonacci sequence of arbitrary length
-
:IEnumerator Fibonacci(int count)  
+
:IEnumerator<int> Fibonacci(int count)  
:{
:{
::int n0=0;
::int n0=0;
::int n1=1;
::int n1=1;
-
::for(int i=0; i<count; i++) {
+
::for(int i=0; i<count; i++)  
-
::yield return n0+n1;
+
::{
-
::n0=n1
+
:::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. 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.
+
The '''iterator block''' is a 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 its underlying iterator on each iteration step.
-
== Chaining Iterators to form Processing Pipelines ==
+
== Chaining Iterators to Processing Pipelines ==
-
;Example of two chained iteration blocks
+
;Example of chaining iteration blocks
-
:IEnumerator OuterIterator()  
+
:IEnumerator<int> FibonacciSquared(int count)
:{
:{
-
::var inner = InnerIterator();
+
::var inner_iterator = Fibonacci(count);
-
::while(inner.MoveNext())
+
::while(inner_iterator.MoveNext())  
-
:::yield return ApplySomeTransformation(inner.Current); // <--- forward the transformed current item returned by the inner iterator.
+
::{
 +
:::int n = inner_iterator.Current;
 +
:::yield return n * n;
 +
::}
:}
:}
-
I would prefer to use the foreach statement over using the IEnumerator interface for the sake of improved readability but sadly this is not directly supported by C#. Writing a custom collection which implements IEnumerable and wraps 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:
+
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 with Iterator<T>
+
;Example of foreach-ing over an iteration block using our featured class Iterator<T>
-
:IEnumerator OuterIterator()  
+
:IEnumerator<int> FibonacciSquared(int count)
:{
:{
-
::foreach(var item in new Iterator( InnerIterator()))
+
::foreach( int n in new Iterator<int>( Fibonacci(count)))
-
:::yield return ApplySomeTransformation(item); // <--- again forward the transformed item to the next step in the pipeline
+
::{
 +
:::yield return n*n;
 +
::}
:}
:}
;Example of using LINQ with an iteration block called Traverse which enumerates all nodes in a tree.
;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 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;
+
:var leaves = from n in nodes where n.IsLeaf() select n;
== class Iterator ==
== class Iterator ==
-
... will be filled in tomorrow
+
Iterator is a generalized wrapper to "convert" IEnumerator into IEnumerable.  
 +
 
 +
<span><span class="S5">public</span><span class="S0"> </span><span class="S5">class</span><span class="S0"> </span>Iterator<span class="S0"> </span><span class="S10">:</span><span class="S0"> </span>IEnumerable<br />
 +
<span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span>IEnumerator<span class="S0"> </span>m_iterator<span class="S10">;</span><br />
 +
<br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S5">public</span><span class="S0"> </span>Iterator<span class="S10">(</span>IEnumerator<span class="S0"> </span>iter<span class="S10">)</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; &nbsp; &nbsp; </span>m_iterator<span class="S0"> </span><span class="S10">=</span><span class="S0"> </span>iter<span class="S10">;</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">}</span><br />
 +
<br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S5">public</span><span class="S0"> </span>Iterator<span class="S10">(</span>IEnumerable<span class="S0"> </span>enumerable<span class="S10">)</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; &nbsp; &nbsp; </span>m_iterator<span class="S0"> </span><span class="S10">=</span><span class="S0"> </span>enumerable<span class="S10">.</span>GetEnumerator<span class="S10">();</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">}</span><br />
 +
<br />
 +
<span class="S0">&nbsp; &nbsp; </span>IEnumerator<span class="S0"> </span>IEnumerable<span class="S10">.</span>GetEnumerator<span class="S10">()</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; &nbsp; &nbsp; </span><span class="S5">return</span><span class="S0"> </span>m_iterator<span class="S10">;</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">}</span><br />
 +
<span class="S10">}</span></span>
 +
 
== class Iterator<T> ==
== class Iterator<T> ==
-
... will be filled in tomorrow
+
The generic version of Iterator.
 +
 
 +
<span><span class="S5">public</span><span class="S0"> </span><span class="S5">class</span><span class="S0"> </span>Iterator<span class="S10">&lt;</span>T<span class="S10">&gt;</span><span class="S0"> </span><span class="S10">:</span><span class="S0"> </span>IEnumerable<span class="S10">&lt;</span>T<span class="S10">&gt;</span><br />
 +
<span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span>IEnumerator<span class="S10">&lt;</span>T<span class="S10">&gt;</span><span class="S0"> </span>m_iterator<span class="S10">;</span><br />
 +
<br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S5">public</span><span class="S0"> </span>Iterator<span class="S10">(</span>IEnumerator<span class="S10">&lt;</span>T<span class="S10">&gt;</span><span class="S0"> </span>iter<span class="S10">)</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; &nbsp; &nbsp; </span>m_iterator<span class="S0"> </span><span class="S10">=</span><span class="S0"> </span>iter<span class="S10">;</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">}</span><br />
 +
<br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S5">public</span><span class="S0"> </span>Iterator<span class="S10">(</span>IEnumerable<span class="S10">&lt;</span>T<span class="S10">&gt;</span><span class="S0"> </span>enumerable<span class="S10">)</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; &nbsp; &nbsp; </span>m_iterator<span class="S0"> </span><span class="S10">=</span><span class="S0"> </span>enumerable<span class="S10">.</span>GetEnumerator<span class="S10">();</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">}</span><br />
 +
<br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S5">public</span><span class="S0"> </span>IEnumerator<span class="S10">&lt;</span>T<span class="S10">&gt;</span><span class="S0"> </span>GetEnumerator<span class="S10">()</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; &nbsp; &nbsp; </span><span class="S5">return</span><span class="S0"> </span>m_iterator<span class="S10">;</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">}</span><br />
 +
<br />
 +
<span class="S0">&nbsp; &nbsp; </span>IEnumerator<span class="S0"> </span>IEnumerable<span class="S10">.</span>GetEnumerator<span class="S10">()</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">{</span><br />
 +
<span class="S0">&nbsp; &nbsp; &nbsp; &nbsp; </span><span class="S5">return</span><span class="S0"> </span><span class="S10">(</span>IEnumerator<span class="S10">)</span>m_iterator<span class="S10">;</span><br />
 +
<span class="S0">&nbsp; &nbsp; </span><span class="S10">}</span><br />
 +
<span class="S10">}</span></span>
 +
 
--[[User:Henon|Henon]] 01:00, 21 April 2008 (CEST)
--[[User:Henon|Henon]] 01:00, 21 April 2008 (CEST)

Latest revision as of 09:35, 22 February 2009

Contents

Ever found yourself writing a custom collection in C# just to be able to pass an IEnumerator into a method that expects IEnumerable? Writing such boilerplate code is never a good idea. The classes Iterator and Iterator<T> presented in this article fill in the missing link from IEnumerator to IEnumerable allowing you to be more productive with iterators.

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:

IEnumerator and Iteration Blocks

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 makes use of C# compiler magic and 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 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 its underlying iterator on each iteration step.

Chaining Iterators to 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)