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 } }
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;} } }
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(" SG.SubGroupName: " + SG.SubGroupName); Console.WriteLine(" SG.Count: " + SG.Count); // Inner foreach to iterate over all Item objects //in the current SubGroup object foreach (Item i in SG) { Console.WriteLine(" i.ItemName: " + i.ItemName); Console.WriteLine(" i.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.