DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.27 Implementing Nested foreach Functionality in a Class

Problem

You need a class that contains an array of objects; each of these objects in turn contains an array of objects. You want to use a nested foreach loop to iterate through all objects in both the outer and inner arrays in the following manner:

foreach (SubSet aSubSet in Set)
{
    foreach (Item i in aSubSet)
    {
        // Operate on Item objects contained in the innermost object collection 
        //  SomeSubSet, which in turn is contained in another outer collection
        //   called Set
    }
}

Solution

Implement IEnumerable on the top-level class as usual, but also implement IEnumerable on each of the objects returned by the top-level enumeration. The following class set contains an ArrayList of SubGroup objects, and each SubGroup object contains an ArrayList of Item objects:

using System;
using System.Collections;

//-----------------------------------------
//
//  The top-level class
//
//-----------------------------------------
public class Set : IEnumerable
{
    //CONSTRUCTORS
    public Set( ) {}

    //FIELDS
    private ArrayList setArray = new ArrayList( );

    //PROPERTIES
    public int Count
    {
        get{return(setArray.Count);}
    }

    //METHODS
    public IEnumerator GetEnumerator( )
    {
        return(new SetEnumerator(this));
    }

    public int AddGroup(string name)
    {
        return(setArray.Add(new SubGroup(name)));
    }

    public SubGroup GetGroup(int setIndex)
    {
        return((SubGroup)setArray[setIndex]);
    }

    //NESTED ITEMS
    public class SetEnumerator : IEnumerator
    {
        //CONSTRUCTORS
        public SetEnumerator(Set theSet)
        {
            setObj = theSet;
        }

        //FIELDS
        private Set setObj;
        private int index = -1;

        //METHODS
        public bool MoveNext( )
        {
            index++;
            if (index >= setObj.Count)
            {
                return(false);
            }
            else
            {
                return(true);
            }
        }

        public void Reset( )
        {
            index = -1;
        }

        public object Current
        {
            get{return(setObj.setArray[index]);}
        }
    }
}

//-----------------------------------------
//
//  The inner class
//
//-----------------------------------------
public class SubGroup : IEnumerable
{
    //CONSTRUCTORS
    public SubGroup( ) {}

    public SubGroup(string name)
    {
        subGroupName = name;
    }

    //FIELDS
    private string subGroupName = "";
    private ArrayList itemArray = new ArrayList( );

    //PROPERTIES
    public string SubGroupName
    {
        get{return(subGroupName);}
    }

    public int Count
    {
        get{return(itemArray.Count);}
    }

    //METHODS
    public int AddItem(string name, int location)
    {
        return(itemArray.Add(new Item(name, location)));
    }

    public Item GetSubGroup(int index)
    {
        return((Item)itemArray[index]);
    }

    public IEnumerator GetEnumerator( )
    {
        return(new SubGroupEnumerator(this));
    }

    //NESTED ITEMS
    public class SubGroupEnumerator : IEnumerator
    {

        //CONSTRUCTORS
        public SubGroupEnumerator(SubGroup SubGroupEnum)
        {
            subGroup = SubGroupEnum;
        }

        //FIELDS
        private SubGroup subGroup;
        private int index = -1;

        //METHODS
        public bool MoveNext( )
        {
            index++;
            if (index >= subGroup.Count)
            {
                return(false);
            }
            else
            {
                return(true);
            }
        }

        public void Reset( )
        {
            index = -1;
        }
    
        public object Current
        {
            get{return(subGroup.itemArray[index]);}
        }
    }
}

//-----------------------------------------
//
//  The lowest-level class
//
//-----------------------------------------
public class Item
{
    //CONSTRUCTOR
    public Item(string name, int location)
    {
        itemName = name;
        itemLocation = location;
    }

    private string itemName = "";
    private int itemLocation = 0;

    public string ItemName
    {
        get {return(itemName);}
        set {itemName = value;}
    }

    public int ItemLocation
    {
        get {return(itemLocation);}
        set {itemLocation = value;}
    }
}

Discussion

Building functionality into a class to allow it to be iterated over using the foreach loop is not extremely difficult; however, building functionality into embedded classes to allow a nested foreach idiom to be used requires keeping careful track of the classes you are building.

The ability of a class to be used by the foreach loop requires the use of two interfaces: IEnumerable and IEnumerator. The IEnumerable interface contains the GetEnumerator method, which accepts no parameters and returns an enumerator object. It is this enumerator object that implements the IEnumerator interface. This interface contains the methods MoveNext and Reset along with the property Current. The MoveNext method accepts no parameters and returns a bool indicating whether the MoveNext method has reached the last element in the collection. The Reset method also accepts no parameters and returns a void. This method simply moves to the position in the collection that is immediately before the first element. Once in this state, the MoveNext method must be called in order to access the first element. The Current property is read-only and returns an object, which is the current element in the collection.

The code for this recipe is divided among five classes. The top-level class is the Set class, which contains an ArrayList of SubGroup objects. The SubGroup object also contains an ArrayList, but this ArrayList contains Item objects. The Set and SubGroup classes each contain a nested class, which is the enumerator class (i.e., it implements IEnumerator). The Set and SubGroup classes both implement the IEnumerable interface. The class structure looks like this:

Set (Implements IEnumerable)
SetEnumerator (Implements IEnumerator and is nested within the Set class)
    SubGroup (Implements IEnumerable)
    SubGroupEnumerator (Implements IEnumerator and is nested within the SubGroup
      class)
        Item

By examining the Set class, we can see how classes usable by a foreach loop are constructed. This class contains:

  • A simple ArrayList, which will be iterated over by the class enumerator.

  • A property, Count, which returns the number of elements in the ArrayList.

  • A method, GetEnumerator, which is defined by the IEnumerable interface. This method returns a SetEnumerator object. As you shall see later, this object allows the foreach loop to do its work.

  • A method, AddGroup, which adds a SubGroup object to the ArrayList.

  • A method, GetGroup, which returns a SubGroup object in the ArrayList.

The SetEnumerator class, which is nested within the Set class, contains:

  • A constructor that accepts a Set object. This Set object will be iterated over by the foreach loop.

  • Two fields to hold the current index (index) and the Set object (setObj).

  • A method, MoveNext, which is defined by the IEnumerator interface. This method moves the current index (index) to the next position in the Set object's ArrayList. If the index is moved past the last element in the ArrayList, a false is returned. Otherwise, the index is incremented by one, and a true is returned.

  • A method Reset, which is defined by the IEnumerator interface. This method moves the current index (index) to a position immediately before the first element in the ArrayList of the Set object (i.e., -1).

  • A method Current, which is defined by the IEnumerator interface. This method returns the SubGroup object in the Set object's ArrayList, which is pointed to by the index field.

To create the SubGroup and SubGroupEnumerator class, we follow the same pattern, except that the SubGroup class contains an ArrayList of Item objects and the SubGroupEnumerator operates on a SubGroup object.

The final class is the Item class. This class is the lowest level of this structure and contains data that has been grouped within the SubGroup objects, all of which is contained in the Set object. There is nothing out of the ordinary with this class; it simply contains data and the means with which to set and retrieve this data.

Using these classes is quite simple. The following method shows how to create a Set object that contains multiple SubGroup objects, which, in turn, contain multiple Item objects:

public void CreateNestedObjects( )
{
    Set topLevelSet = new Set( );

    // Create two groups under the TopLevelSet object
    topLevelSet.AddGroup("sg1");
    topLevelSet.AddGroup("sg2");

    // For each SubGroup object in the TopLevelSet object, add two Item objects
    foreach (SubGroup SG in TopLevelSet)
    {
        SG.AddItem("item1", 100);
        SG.AddItem("item2", 200);
    }
}

The CreateNestedObjects method first creates a topLevelSet object and creates two SubGroups within it called sg1 and sg2. Each of these SubGroup objects in turn is filled with two Item objects called item1 and item2.

The next method shows how to read all of the Item objects contained within the Set object that was created in the CreateNestedObjects method:

public void ReadNestedObjects(Set TopLevelSet)
{
    Console.WriteLine("TopLevelSet.Count: " + TopLevelSet.Count);

    // Outer foreach to iterate over all SubGroup objects 
    //in the Set object
    foreach (SubGroup SG in TopLevelSet)
    {
        Console.WriteLine("\tSG.SubGroupName:  " + SG.SubGroupName);
        Console.WriteLine("\tSG.Count: " + SG.Count);

        // Inner foreach to iterate over all Item objects 
        //in the current SubGroup object
        foreach (Item i in SG)
        {
            Console.WriteLine("\t\ti.ItemName:     " + i.ItemName);
            Console.WriteLine("\t\ti.ItemLocation: " + i.ItemLocation);
        }
    }
}

This method displays the following:

TopLevelSet.Count: 2
        SG.SubGroupName:  sg1
        SG.Count: 2
                I.ItemName:     item1
                I.ItemLocation: 100
                I.ItemName:     item2
                I.ItemLocation: 200
        SG.SubGroupName:  sg2
        SG.Count: 2
                I.ItemName:     item1
                I.ItemLocation: 100
                I.ItemName:     item2
                I.ItemLocation: 200

The outer foreach loop is used to iterate over all SubGroup objects that are stored in the top-level Set object. The inner foreach loop is used to iterate over all Item objects that are stored in the current SubGroup object.

See Also

See the "IEnumerable Interface" and "IEnumerator Interface" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section