You need to add
foreach
support to a class, but the normal way of
adding an IEnumerator
class is not flexible
enough. Instead of simply iterating from the first element to the
last, you also need to iterate from the last to the first, and you
need to be able to step over, or skip, a predefined number of
elements on each iteration. All of these types of iterators should be
available to your class.
The following interfaces allow polymorphic use of the
foreach
method:
using System.Collections; public interface IRevEnumerator { IEnumerator GetEnumerator( ); } public interface IStepEnumerator { IEnumerator GetEnumerator( ); }
The following class acts as a container for a private
ArrayList
called InternalList
and is used in the foreach
loop to iterate through
the private
InternalList
:
public class Container : IEnumerable, IRevEnumerator, IStepEnumerator { public Container( ) { // Add dummy data to this class internalList.Add(-1); internalList.Add(1); internalList.Add(2); internalList.Add(3); internalList.Add(4); internalList.Add(5); internalList.Add(6); internalList.Add(7); internalList.Add(8); internalList.Add(9); internalList.Add(10); internalList.Add(200); internalList.Add(500); } private ArrayList internalList = new ArrayList( ); private int step = 1; IEnumerator IEnumerable.GetEnumerator( ) { return (new ContainerIterator(this)); } IEnumerator IRevEnumerator.GetEnumerator( ) { return (new RevContainerIterator(this)); } IEnumerator IStepEnumerator.GetEnumerator( ) { return (new StepContainerIterator(this, step)); } public int ForeachStep { get {return (step);} set {step = value;} } public ArrayList List { get {return (internalList);} set {internalList = value;} } // Nested classes // This class iterates from the first element to the last element // in the internalList public class ContainerIterator : IEnumerator { public ContainerIterator(Container c) { this.c = c; Reset( ); } private int index = -1; private Container c = null; public void Reset( ) { index = -1; } public object Current { get {return (c.internalList[index]);} } public bool MoveNext( ) { ++index; if (index < c.internalList.Count) { return (true); } else { return (false); } } } // This class iterates from the last element to the first element // in the internalList public class RevContainerIterator : IEnumerator { public RevContainerIterator(Container c) { this.c = c; Reset( ); } private int index = -1; private Container C = null; public void Reset( ) { index = c.internalList.Count; } public object Current { get {return (c.internalList[index]);} } public bool MoveNext( ) { --index; if (index >= 0) { return (true); } else { return (false); } } } // This class iterates from the first element to the last element // int the internalList and skips a predefined number of elements // in the internalList on each iteration public class StepContainerIterator : IEnumerator { public StepContainerIterator(Container c, int step) { this.c = c; this.step = step; Reset( ); } private int index = -1; private int step = 1; private Container c = null; public void Reset( ) { index = -1; } public object Current { get {return (c.internalList[index]);} } public bool MoveNext( ) { if (index == -1) { ++index; } else { index += step; } if (index < c.internalList.Count) { return (true); } else { return (false); } } } }
The iterator design pattern provides an easy method of moving from item to
item contained within an object. This object could be an array, a
collection, or some other similar type of container. This technique
is similar to using a for
loop to iterate over
each item contained in an array. The difference is that, with the
iterator design pattern, you do not need advance knowledge about how
the elements are stored in the container or where the elements are
located in the container. In contrast, when using a
for
loop, you need to know what elements are
stored in the container. And you need some type of direct or indirect
access, via a method or indexer, to the contained list of elements.
This is not so with the foreach
loop.
The
FCL provides two special interfaces, IEnumerable
and IEnumerator
, that allow us to easily implement
this design pattern. The IEnumerable
interface
defines a single method:
IEnumerator GetEnumerator( )
This method accepts no parameters and returns an
IEnumerator
interface object. The
IEnumerator
type that is returned is another
interface that has the following property and two methods:
object Current {get;} bool MoveNext( ) void Reset( )
The Current
method returns the current element
being accessed. The MoveNext
method moves to the
next element. If this method successfully moves to the next element,
it returns true
. If there are no more elements in
the list, it returns false
as an indication to
stop the iteration. The Reset
method resets the
current element pointer to the position immediately before the first
element in the list.
Implementing these two interfaces allows us to use a familiar looping
mechanism: the foreach
loop. With the
foreach
loop, you do not have to worry about
moving the current element pointer to the beginning of the list or
even about incrementing this pointer as you move through the list. In
addition, you do not have to watch for the end of the list,
preventing you from going beyond the bounds of the list. The best
part about the foreach
loop and the iterator
pattern is that you do not have to know how to access the list of
elements within its container—indeed, you do not even have to
have access to the list of elements; the
IEnumerator
and IEnumerable
interfaces implemented on the container do this for you.
The Container
class contains a private
ArrayList
of items called
internalList
. The GetEnumerator
method on this class is implemented to return a class called
ContainerIterator
, which is nested within the
Container
class. This
ContainerIterator
class implements the
IEnumerator
interface and contains all of the
intelligence to control how the foreach
loop
operates and what data it operates on.
The ContainerIterator
class uses a private
variable, index
, as the pointer to the current
element in the Container.internalList
ArrayList
. Remember that this
ArrayList
is private and cannot be seen by the
client code. A second private variable, c
, holds a
pointer to the outer class Container
. A nested
class is used because it can see all private members of the outer
class; therefore, it is not a problem to access the
internalList
from the nested
ContainerIterator
class.
The ContainerIterator
class is designed to move
from the first element in the ArrayList
to the
last. The foreach
loop may not change this.
However, we may add code to the ContainerIterator
class to move across the elements in the ArrayList
in different manners. For example, if we always wanted to iterate the
ArrayList
in ascending order, we could modify the
ContainerIterator
class constructor by calling the
ArrayList.Sort
method, as follows:
public ContainerIterator(Container c) { this.c = c; Reset( ); c.internalList.Sort( ); }
The ContainerIterator
may also be modified to
start at the last element in the ArrayList
and
work its way to the first element. The
RevContainerIterator
class demonstrates this. A
third class called StepContainerIterator
demonstrates a way to step over a specified number of elements in the
ArrayList
. This is similar to the following VB.NET
and C# code:
' VB.NET Code For i = 0 to 100 Step 2 ... Next // C# Code for (int i = 0; i <= 100; i += 2) {...}
In both of these loops, every other element in the list is skipped.
The StepContainerIterator
allows this by accepting
an integer value in its constructor that determines how many items
will be skipped. This is the step
parameter in this constructor.
There is also a way to choose which IEnumerator
interface to use with a foreach
loop. Since the
GetEnumerator
method returns only an
IEnumerator
interface instead of a concrete
object, we can nest all three of these IEnumerator
type classes within our Container
class and tell
the foreach
loop which iterator it will use.
To enable this, our Container
class needs to
implement three distinct interfaces, IEnumerator
,
IRevEnumerator
, and
IStepEnumerator
. The
IEnumerator
interface is defined by the FCL.
Notice that all three interfaces define the same
GetEnumerator
method. The
Container
class implements the methods on these
three interfaces as explicit interface methods. This allows us to
choose which GetInterface
method to use by casting
the Container
class to one of these three
interface types.
To use the ContainerIterator
, we do not have to do
anything to the foreach
loop; this is the default
IEnumerator
type that is returned by
GetEnumerator
. The code for this is as follows:
Container cntnr = new Container( ); foreach (int i in cntnr) { Console.WriteLine(i); }
If we do not know that cntnr
contains an
ArrayList
of integers, we could write the
following to iterate over each element:
foreach (object i in cntnr) { Console.WriteLine(i.ToString( )); }
To use RevContainerIterator
, we cast the
cntnr
object to the interface that has the
GetEnumerator
method to return a
RevContainerIterator
. This code is written as
follows:
Container cntnr = new Container( ); foreach (int i in ((IRevEnumerator)cntnr)) { Console.WriteLine(i); }
Again, to use the StepContainerIterator
, we cast
the cntnr
object to the correct interface,
IStepEnumerator
. This code is written as follows:
Container cntnr = new Container( ); cntnr.ForeachStep = 2; foreach (int i in ((IStepEnumerator)cntnr)) { Console.WriteLine(i); }
Notice the extra step with this interface: the
ForeachStep
property in the
Container
object needs to be set to an integer
value. This value then is passed to the
StepContainerIterator
constructor to be used in
skipping over that number of elements in the list.