The .NET Framework provides standard interfaces for enumerating, comparing, and creating collections. The key collection interfaces are listed in Table 9-2.
Table 9-2. Collection interfaces
You 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 myLBT
. 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, you return
false
; otherwise you return
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 class
namespace Programming_CSharp { using System; using System.Collections; // a simplified ListBox control public class ListBoxTest : IEnumerable {// private implementation of ListBoxEnumerator
private class ListBoxEnumerator : IEnumerator
{
// 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]);
}
}
private ListBoxTest lbt;
private int index;
}
// Enumerable classes can return an enumerator
public IEnumerator GetEnumerator( )
{
return (IEnumerator) new ListBoxEnumerator(this);
}
// initialize the list box 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 list box 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; } private string[] strings; private int ctr = 0; } public class Tester { static void Main( ) { // create a new list box 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 room for 8 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 list box’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 list box have been displayed.
Another key interface for arrays, and for
all the collections provided by the .NET Framework, is
ICollection
. ICollection
provides four properties: Count
,
IsReadOnly
, IsSynchronized
, and
SyncRoot
. ICollection
also
provides one public method, CopyTo( )
. We’ll
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
myIntArray
to determine how many objects are in it
so that you can print their values.
The
IComparer
interface provides the Compare( )
method, by which any two items in a collection can be
ordered.
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 IComparable
and
provides a default case-sensitive implementation. You’ll see
how to create your own implementation of
IComparable
in the next section on
ArrayLists
.
3.145.9.148