[ Team LiB ] |
9.4 Collection InterfacesThe .NET Framework provides standard interfaces for enumerating, comparing, and creating collections. The key collection interfaces are listed in Table 9-2.
9.4.1 The IEnumerable InterfaceYou can support the foreach statement in ListBoxTest by implementing the IEnumerable interface. IEnumerable has only one method, GetEnumerator( ), whose job is to return a specialized implementation of IEnumerator. Thus, the semantics of an Enumerable class are that it can provide an Enumerator: public IEnumerator GetEnumerator( ) { return (IEnumerator) new ListBoxEnumerator(this); } The Enumerator must implement the IEnumerator methods and properties. These can be implemented either directly by the container class (in this case, ListBoxTest) or by a separate class. The latter approach is generally preferred, because it encapsulates this responsibility in the Enumerator class rather than cluttering up the container. Because the Enumerator class is specific to the container class (that is, because ListBoxEnumerator must know a lot about ListBoxTest), you will make it a private implementation, contained within ListBoxTest. Notice that the method passes the current ListBoxTest object (this) to the enumerator, which will allow the enumerator to enumerate this particular ListBoxTest object. The class to implement the Enumerator is implemented here as ListBoxEnumerator, which is a private class defined within ListBoxTest. Its work is fairly straightforward. It must implement the public instance property Current and two public instance methods, MoveNext( ) and Reset( ). The ListBoxTest to be enumerated is passed in as an argument to the constructor, where it is assigned to the member variable lbt. The constructor also sets the member variable index to -1, indicating that you have not yet begun to enumerate the object: public ListBoxEnumerator(ListBoxTest lbt) { this.lbt = lbt; index = -1; } The MoveNext( ) method increments the index and then checks to ensure that you've not run past the end of the object you're enumerating. If you have, the program returns false; otherwise it returns true: public bool MoveNext( ) { index++; if (index >= lbt.strings.Length) return false; else return true; } The IEnumerator method Reset( ) does nothing but reset the index to -1. The property Current is implemented to return the current string. This is an arbitrary decision; in other classes, Current will have whatever meaning the designer decides is appropriate. However defined, every enumerator must be able to return the current member, as accessing the current member is what enumerators are for: public object Current { get { return(lbt[index]); } } That's all there is to it: the call to foreach fetches the enumerator and uses it to enumerate over the array. Because foreach will display every string—whether or not you've added a meaningful value—change the initialization of strings to 8 to keep the display manageable, as shown in Example 9-11. Example 9-11. Making a ListBox an enumerable classnamespace Programming_CSharp { using System; using System.Collections; // a simplified ListBox control public class ListBoxTest : IEnumerable { private string[] strings; private int ctr = 0; // private implementation of ListBoxEnumerator private class ListBoxEnumerator : IEnumerator { private ListBoxTest lbt; private int index; // public within the private implementation // thus, private within ListBoxTest public ListBoxEnumerator(ListBoxTest lbt) { this.lbt = lbt; index = -1; } // Increment the index and make sure the // value is valid public bool MoveNext( ) { index++; if (index >= lbt.strings.Length) return false; else return true; } public void Reset( ) { index = -1; } // Current property defined as the // last string added to the listbox public object Current { get { return(lbt[index]); } } } // Enumerable classes can return an enumerator public IEnumerator GetEnumerator( ) { return (IEnumerator) new ListBoxEnumerator(this); } // initialize the listbox with strings public ListBoxTest(params string[] initialStrings) { // allocate space for the strings strings = new String[8]; // copy the strings passed in to the constructor foreach (string s in initialStrings) { strings[ctr++] = s; } } // add a single string to the end of the listbox public void Add(string theString) { strings[ctr] = theString; ctr++; } // allow array-like access public string this[int index] { get { if (index < 0 || index >= strings.Length) { // handle bad index } return strings[index]; } set { strings[index] = value; } } // publish how many strings you hold public int GetNumEntries( ) { return ctr; } } public class Tester { static void Main( ) { // create a new listbox and initialize ListBoxTest lbt = new ListBoxTest("Hello", "World"); // add a few strings lbt.Add("Who"); lbt.Add("Is"); lbt.Add("John"); lbt.Add("Galt"); // test the access string subst = "Universe"; lbt[1] = subst; // access all the strings foreach (string s in lbt) { Console.WriteLine("Value: {0}", s); } } } } Output: Value: Hello Value: Universe Value: Who Value: Is Value: John Value: Galt Value: Value: The program begins in Main( ), creating a new ListBoxTest object and passing two strings to the constructor. When the object is created, an array of Strings is created with enough room for eight strings. Four more strings are added using the Add method, and the second string is updated, just as in the previous example. The big change in this version of the program is that a foreach loop is called, retrieving each string in the list box. The foreach loop automatically uses the IEnumerable interface, invoking GetEnumerator( ). This gets back the ListBoxEnumerator whose constructor is called, thus initializing the index to -1. The foreach loop then invokes MoveNext( ), which immediately increments the index to 0 and returns true. The foreach then uses the Current property to get back the current string. The Current property invokes the listbox's indexer, getting back the string stored at index 0. This string is assigned to the variable s defined in the foreach loop, and that string is displayed on the console. The foreach loop repeats these steps (MoveNext( ), Current, display) until all the strings in the listbox have been displayed. 9.4.2 The ICollection InterfaceAnother key interface for arrays, and for all the collections provided by the .NET Framework, is ICollection. ICollection provides three properties: Count, IsSynchronized, and SyncRoot. ICollection provides one public method as well, CopyTo( ). We look at the CopyTo( ) method later in this chapter. The property used most often is Count, which returns the number of elements in the collection: For (int i = 0;i<myIntArray.Count;i++) { //... } Here you are using the Count property of myIntArray to determine how many objects are in it so that you can print their values. 9.4.3 The IComparer and IComparable InterfacesThe IComparer interface provides the Compare( ) method, by which any two items in a collection can be ordered. You can implement IComparer in helper classes that you pass to overloaded methods such as Array.Sort(Array a, IComparer c). The IComparable interface is similar, but it defines CompareTo( ) on the object to be compared rather than on a helper class. The Compare( ) method is typically implemented by calling the CompareTo( ) method of one of the objects. CompareTo is a method of all objects that implement IComparable. If you want to create classes that can be sorted within a collection, you will need to implement IComparable. The .NET Framework provides a Comparer class that implements IComparer and provides a default case-sensitive implementation. You'll see how to create your own implementations of IComparer and IComparable in the next section on ArrayLists. |
[ Team LiB ] |