Recipe 3.27 Implementing Nested foreach Functionality in a ClassProblemYou 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 } } SolutionImplement 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;} } } DiscussionBuilding 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:
The SetEnumerator class, which is nested within the Set class, contains:
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 AlsoSee the "IEnumerable Interface" and "IEnumerator Interface" topics in the MSDN documentation. |