DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.34 Creating Custom Enumerators

Problem

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.

Solution

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);
            }
        }
    }
}

Discussion

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.

See Also

See the "IEnumerator Interface," "Using foreach with Collections," and "Collection Classes Tutorial" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section