16.4 Implement an Enumerable Type


Problem

You need to create a collection type whose contents you can enumerate using a foreach statement.

Solution

Implement the interface System.IEnumerable on your collection type. The GetEnumerator method of the IEnumerable interface returns an enumerator ” an object that implements the interface System.IEnumerator . The IEnumerator interface defines the methods used by the foreach statement to enumerate the collection.

Discussion

A numerical indexer allows you to iterate through the elements of a collection using a for loop. However, this technique doesn't always provide an appropriate abstraction for nonlinear data structures, such as trees and multidimensional collections. The foreach statement provides an easy-to-use and syntactically elegant mechanism for iterating through a collection of objects regardless of their internal structures.

In order to support foreach semantics, the object containing the collection of objects must implement the System.IEnumerable interface. The IEnumerable interface declares a single method named GetEnumerator , which takes no arguments and returns an object that implements System.IEnumerator , as shown here.

 IEnumerator GetEnumerator(); 

The IEnumerator instance returned by GetEnumerator is the object that actually supports enumeration of the collection's data elements. The IEnumerator interface provides a read-only, forward-only cursor for accessing the members of the underlying collection. Table 16.1 describes the members of the IEnumerator interface.

Table 16.1: Members of the IEnumerator Interface

Member

Description

Current

Property that returns the current data element. When the enumerator is created, Current refers to a position preceding the first data element. This means you must call MoveNext before using Current . If Current is called and the enumerator is positioned before the first element or after the last element in the data collection, Current must throw a System.InvalidOperationException .

MoveNext

Method that moves the enumerator to the next data element in the collection. Returns true if there are more elements; otherwise , it returns false . If the underlying source of data changes during the life of the enumerator, MoveNext must throw an InvalidOperationException .

Reset

Method that moves the enumerator to a position preceding the first element in the data collection. If the underlying source of data changes during the life of the enumerator, Reset must throw an InvalidOperationException .

The TeamMember , Team , and TeamMemberEnumerator classes demonstrate the implementation of the IEnumerable and IEnumerator interfaces. The TeamMember class (listed here) represents a member of a team.

 // TeamMember class represents an individual team member. public class TeamMember {          public string Name;     public string Title;     // Simple TeamMember constructor.     public TeamMember(string name, string title) {         Name = name;         Title = title;     }              // Returns a string representation of the TeamMember.     public override string ToString() {                  return string.Format("{0} ({1})", Name, Title);     }         } 

The Team class, which represents a team of people, is a collection of TeamMember objects. Team implements the IEnumerable interface and declares a separate class, named TeamMemberEnumerator , to provide enumeration functionality. Often, collection classes will implement both the IEnumer able and IEnumerator interfaces directly. However, the use of a separate enumerator class is the simplest approach to allow multiple enumerators ”and multiple threads ”to enumerate the Team concurrently.

Team implements the Observer Pattern using delegate and event members to notify all TeamMemberEnumerator objects if their underlying Team changes. (See recipe 16.10 for a detailed description of the Observer Pattern.) The TeamMemberEnumerator class is a private nested class, so you can't create instances of it other than through the Team.GetEnumerator method. Here is the code for the Team and TeamMemberEnumerator classes.

 // Team class represents a collection of TeamMember objects. Implements // the IEnumerable interface to support enumerating TeamMember objects. public class Team : IEnumerable {     // TeamMemberEnumerator is a private nested class that provides     // the functionality to enumerate the TeamMembers contained in      // a Team collection. As a nested class, TeamMemberEnumerator     // has access to the private members of the Team class.     private class TeamMemberEnumerator : IEnumerator {         // The Team that this object is enumerating.         private Team sourceTeam;         // Boolean to indicate whether underlying Team has changed         // and so is invalid for further enumeration.         private bool teamInvalid = false;         // Integer to identify the current TeamMember. Provides         // the index of the TeamMember in the underlying ArrayList         // used by the Team collection. Initialize to -1, which is          // the index prior to the first element.         private int currentMember = -1;         // Constructor takes a reference to the Team that is the source         // of enumerated data.         internal TeamMemberEnumerator(Team team) {             this.sourceTeam = team;             // Register with sourceTeam for change notifications             sourceTeam.TeamChange +=                  new TeamChangedEventHandler(this.TeamChange);         }         // Implement the IEnumerator.Current property.         public object Current {             get {                  // If the TeamMemberEnumerator is positioned before                  // the first element or after the last element then                  // throw an exception.                 if (currentMember == -1                      currentMember > (sourceTeam.teamMembers.Count-1)) {                     throw new InvalidOperationException();                 }                 //Otherwise, return the current TeamMember                 return sourceTeam.teamMembers[currentMember];             }         }         // Implement the IEnumerator.MoveNext method.         public bool MoveNext() {             // If underlying Team is invalid, throw exception             if (teamInvalid) {                                  throw new InvalidOperationException("Team modified");             }                          // Otherwise, progress to the next TeamMember             currentMember++;             // Return false if we have moved past the last TeamMember             if (currentMember > (sourceTeam.teamMembers.Count-1)) {                 return false;             } else {                 return true;             }         }         // Implement the IEnumerator.Reset method.         // This method resets the position of the TeamMemberEnumerator         // to the beginning of the Team collection.         public void Reset() {             // If underlying Team is invalid, throw exception             if (teamInvalid) {                                  throw new InvalidOperationException("Team modified");             }             // Move the currentMember pointer back to the index             // preceding the first element.             currentMember = -1;         }         // An event handler to handle notifications that the underlying         // Team collection has changed.         internal void TeamChange(Team t, EventArgs e) {                          // Signal that the underlying Team is now invalid             teamInvalid = true;         }     }     // A delegate that specifies the signature that all team change event     // handler methods must implement.     public delegate void TeamChangedEventHandler(Team t, EventArgs e);     // An ArrayList to contain the TeamMember objects     private ArrayList teamMembers;     // The event used to notify TeamMemberEnumerators that the Team     // has changed.      public event TeamChangedEventHandler TeamChange;     // Team constructor     public Team() {                  teamMembers = new ArrayList();     }     // Implement the IEnumerable.GetEnumerator method.     public IEnumerator GetEnumerator() {         return new TeamMemberEnumerator(this);     }     // Adds a TeamMember object to the Team     public void AddMember(TeamMember member) {              teamMembers.Add(member);         // Notify listeners that the list has changed         if (TeamChange != null) {                          TeamChange(this, null);         }     } } 

If your collection class contains different types of data that you want to enumerate separately, implementing the IEnumerable interface on the collection class is insufficient. In this case, you would implement a number of properties that returned different IEnumerator instances. For example, if the Team class represented both the team members and hardware assigned to the team, you might implement properties like those shown here.

 // Property to enumerate team members public IEnumerator Members {     get {         return new TeamMemberEnumerator(this);     } } // Property to enumerate team computers public IEnumerator Computers {     get {         return new TeamComputerEnumerator(this);     } } 

To use these different enumerators, you would use the following code:

 Team team = new Team(); foreach(TeamMember in team.Members) {     // Do something } foreach(TeamComputer in team.Computers) {     // Do something } 
Note  

The foreach statement also supports types that implement a pattern equivalent to that defined by the IEnumerable and IEnumerator interfaces, even though the type doesn't implement the interfaces. However, your code is clearer and more easily understood if you implement the IEnumerable interface. See the C# Language Specification for details on the exact requirements of the foreach statement ( http://msdn.microsoft.com/net/ecma/ ).




C# Programmer[ap]s Cookbook
C# Programmer[ap]s Cookbook
ISBN: 735619301
EAN: N/A
Year: 2006
Pages: 266

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net