// 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 Addstri
Trang 1The example begins by creating myArray, an array of strings with the words:
"Who", "is", "John", "Galt"
This array is printed, and then passed to the Array.Reverse( ) method, where it is printed again to see that the array itself has been reversed:
Value: Galt
Value: John
Value: is
Value: Who
Similarly, the example creates a second array, myOtherArray, containing the words:
"We", "Hold", "These", "Truths",
"To", "Be", "Self", "Evident",
This is passed to the Array.Sort( ) method Then Array.Sort( ) happily sorts them alphabetically:
Trang 29.3 Indexers
There are times when it is desirable to access a collection within a class as though the class itself were an array For example, suppose you create a list box control named myListBox that contains a list of strings stored in a one-dimensional array, a private member variable named
myStrings A list box control contains member properties and methods in addition to its array
of strings However, it would be convenient to be able to access the list box array with an index, just as if the list box were an array For example, such a property would permit statements like the following:
string theFirstString = myListBox[0];
string theLastString = myListBox[Length-1];
An indexer is a C# construct that allows you to access collections contained by a class using
the familiar [] syntax of arrays An indexer is a special kind of property and includes get( )
and set( ) methods to specify its behavior
You declare an indexer property within a class using the following syntax:
type this [type argument]{get; set;}
The return type determines the type of object that will be returned by the indexer, while the type argument specifies what kind of argument will be used to index into the collection that contains the target objects Although it is common to use integers as index values, you can index a collection on other types as well, including strings You can even provide an indexer with multiple parameters to create a multidimensional array!
The this keyword is a reference to the object in which the indexer appears As with a normal property, you also must define get( ) and set( ) methods, which determine how the requested object is retrieved from or assigned to its collection
Example 9-9 declares a list box control (ListBoxTest), which contains a simple array (myStrings) and a simple indexer for accessing its contents
C++ programmers take note: the indexer serves much the same purpose
as overloading the C++ index operator ([]) The index operator cannot
be overloaded in C#, which provides the indexer in its place
Example 9-9 Using a simple indexer
namespace Programming_CSharp
{
using System;
// a simplified ListBox control
public class ListBoxTest
{
// initialize the list box with strings
public ListBoxTest(params string[] initialStrings)
{
Trang 3// 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)
// allow array-like access
public string this[int index]
// publish how many strings you hold
public int GetNumEntries( )
Trang 4// 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
for (int i = 0;i<lbt.GetNumEntries( );i++)
of course, these are a small fraction of the total methods of a list box, whose principal job is to display the strings and enable user choice
The first thing to notice is the two private members:
private string[] strings;
private int ctr = 0;
In this program, the list box maintains a simple array of strings: strings Again, in a real list box you might use a more complex and dynamic container, such as a hash table (described later in this chapter) The member variable ctr will keep track of how many strings have been added to this array
Initialize the array in the constructor with the statement:
strings = new String[256];
The remainder of the constructor adds the parameters to the array Again, for simplicity, simply add new strings to the array in the order received
Because you cannot know how many strings will be added, use the keyword params, as described earlier in this chapter
Trang 5The Add( ) method of ListBoxTest does nothing more than append a new string to the internal array
The key method of ListBoxTest, however, is the indexer An indexer is unnamed, so use the
this keyword:
public string this[int index]
The syntax of the indexer is very similar to that for properties There is either a get( )
method, a set( ) method, or both In the case shown, the get( ) method endeavors to implement rudimentary bounds checking, and assuming the index requested is acceptable, it returns the value requested:
9.3.1 Indexers and Assignment
In Example 9-9 , you cannot assign to an index that does not have a value Thus, if you write:
lbt[10] = "wow!";
you would trigger the error handler in the set( ) method, which would note that the index you've passed in (10) is larger than the counter (6)
Trang 6Of course, you can use the set( ) method for assignment; you simply have to handle the indexes you receive To do so, you might change the set( ) method to check the Length of the buffer rather than the current value of counter If a value was entered for an index that did not yet have a value, you would update ctr:
ListBoxTest lbt = new ListBoxTest("Hello", "World");
Then call Add( ) to add four more strings:
// add a few strings
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("John");
lbt.Add("Galt");
Before examining the values, modify the second value (at index 1):
string subst = "Universe";
Trang 7Finally, display each value in a loop:
for (int i = 0;i<lbt.GetNumEntries( );i++)
{
Console.WriteLine("lbt[{0}]: {1}",i,lbt[i]);
}
9.3.2 Indexing on Other Values
C# does not require that you always use an integer value as the index to a collection When you create a custom collection class and create your indexer, you are free to create indexers that index on strings and other types In fact, the index value can be overloaded so that a given collection can be indexed, for example, by an integer value or by a string value, depending on the needs of the client
In the case of our list box, we might want to be able to index into the list box based on a string Example 9-10 illustrates a string index The indexer calls findString( ), which is a helper method that returns a record based on the value of the string provided Notice that the overloaded indexer and the indexer from Example 9-9 are able to coexist
Example 9-10 Overloading an index
namespace Programming_CSharp
{
using System;
// a simplified ListBox control
public class ListBoxTest
{
// initialize the list box with strings
public ListBoxTest(params string[] initialStrings)
{
// allocate space for the strings
strings = new String[256];
// 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++;
}
Trang 8// allow array-like access
public string this[int index]
// publish how many strings you hold
public int GetNumEntries( )
Trang 9public class Tester
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;
lbt["Hel"] = "GoodBye";
// lbt["xyz"] = "oops";
// access all the strings
for (int i = 0;i<lbt.GetNumEntries( );i++)
We see in Main( ) that the user passes in a string segment to the index, just as was done with
Trang 10myStrings[findString(index)] = value;
The careful reader will note that if the string does not match, a value of
-1 is returned, which is then used as an index into myStrings This action then generates an exception (System.NullReferenceException), as you can see by uncommenting the following line in Main:
IEnumerable Enumerates through a collection using a foreach statement
ICollection Implemented by all collections to provide the CopyTo( ) method as well as
the Count, IsSynchronized, and SyncRoot properties
IComparer Compares two objects held in a collection so that the collection can be sorted IList Used by array-indexable collections
IDictionary Used for key/value-based collections such as Hashtable and SortedList IDictionaryEnumerator Allows enumeration with foreach of a collection that supports
IDictionary
9.4.1 The IEnumerable Interface
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( )
Trang 11Because 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 bool MoveNext( )
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
Trang 12meaningful value change the initialization of strings to 8 to keep the display manageable,
// 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
// Current property defined as the
// last string added to the listbox
public object Current
// Enumerable classes can return an enumerator
public IEnumerator GetEnumerator( )
{
return (IEnumerator) new ListBoxEnumerator(this);
}
Trang 13// 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]
// publish how many strings you hold
public int GetNumEntries( )
new ListBoxTest("Hello", "World");
// add a few strings
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("John");
lbt.Add("Galt");
Trang 14// test the access
string subst = "Universe";
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 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
9.4.2 The ICollection Interface
Another key interface for arrays, and for all the collections provided by the NET Framework,
is ICollection ICollection provides four 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++)
{
//
}
Trang 15Here 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 Interfaces
The 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 Compare( ) 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
9.5 Array Lists
The classic problem with the Array type is its fixed size If you do not know in advance how many objects an array will hold, you run the risk of declaring either too small an array (and running out of room) or too large an array (and wasting memory)
Your program might be asking the user for input, or gathering input from a web site As it finds objects (strings, books, values, etc.), you will add them to the array, but you have no idea how many objects you'll collect in any given session The classic fixed-size array is not a good choice, as you can't predict how large an array you'll need
The ArrayList class is an array whose size is dynamically increased as required
ArrayLists provide a number of useful methods and properties for their manipulation Some
of the most important are shown in Table 9-3
Table 9-3 ArrayList methods and properties Method or property Purpose
Adapter() Public static method that creates an ArrayList wrapper for an IList object
FixedSize() Overloaded public static method that returns a list object as a wrapper The list is of fixed size; elements can be modified but not added or removed ReadOnly() Overloaded public static method that returns a list class as a wrapper, allowing read-only access Repeat() Public static method that returns an ArrayList whose elements are copies of
the specified value
Synchronized() Overloaded public static method that returns a list wrapper that is thread-safe
Capacity Property to get or set the number of elements the ArrayList can contain
Count Property to get the number of elements currently in the array
IsFixedSize Property to get to find out if the ArrayList is of fixed size
IsReadOnly Property to get to find out if the ArrayList is read-only
Trang 16IsSynchronized Property to get to find out if the ArrayList is thread-safe
Item() Gets or sets the element at the specified index This is the indexer for the ArrayList
Clear() Removes all elements from the ArrayList
Clone() Creates a shallow copy
Contains() Determines if an element is in the ArrayList
CopyTo() Overloaded public method that copies an ArrayList to a one-dimensional array GetEnumerator() Overloaded public method that returns an enumerator to iterate an ArrayList
GetRange() Copies a range of elements to a new ArrayList
IndexOf() Overloaded public method that returns the index of the first occurrence of a value Insert() Inserts an element into ArrayList
InsertRange() Inserts the elements of a collection into the ArrayList
LastIndexOf() Overloaded public method that returns the index of the last occurrence of a value in
the ArrayList
Remove() Removes the first occurrence of a specific object
RemoveAt() Removes the element at the specified index
RemoveRange() Removes a range of elements
Reverse() Reverses the order of elements in the ArrayList
SetRange() Copies the elements of a collection over a range of elements in the ArrayList
Sort() Sorts the ArrayList
ToArray() Copies the elements of the ArrayList to a new array
TrimToSize() Sets the capacity to the actual number of elements in the ArrayList
When you create an ArrayList, you do not define how many objects it will contain Add to the ArrayList using the Add( ) method, and the list takes care of its own internal bookkeeping, as illustrated in Example 9-12
Example 9-12 Working with an ArrayList
// a simple class to store in the array
public class Employee
Trang 17public override string ToString( )
ArrayList empArray = new ArrayList( );
ArrayList intArray = new ArrayList( );
// populate the array
for (int i = 0;i<5;i++)
// print all the contents
for (int i = 0;i<intArray.Count;i++)
{
Console.Write("{0} ", intArray[i].ToString( ));
}
Console.WriteLine("\n");
// print all the contents of the Employee array
for (int i = 0;i<empArray.Count;i++)
Trang 18how many objects the ArrayList will hold The ArrayList has a property, Capacity, which
is the number of elements the ArrayList is capable of storing:
public int Capcity {virtual get; virtual set; }
The default capacity is 16 When you add the 17th element, the capacity is automatically doubled to 32 If you change the for loop to:
for (int i = 0;i<17;i++)
the output looks like this:
0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
empArray.Capacity: 32
You can manually set the capacity to any number equal to or greater than the count If you set
it to a number less than the count, the program will throw an exception of type
ArgumentOutOfRangeException
9.5.1 Implementing IComparable
Like all collections, the ArrayList implements the Sort( ) method, which allows you to sort any objects that implement IComparable In the next example, you'll modify the
Employee object to implement IComparable:
public class Employee : IComparable
To implement the IComparable interface, the Employee object must provide a CompareTo( )
Because int derives from object, it has methods, including the method CompareTo( ) Thus int is an object to which you may delegate the responsibility of comparison
Trang 19You are now ready to sort the array list of employees, empList To see if the sort is working, you'll need to add integers and Employee instances to their respective arrays with random values To create the random values, you'll instantiate an object of class Random; to generate the random values you'll call the Next( ) method on the Random object, which returns a pseudorandom number The Next( ) method is overloaded; one version allows you to pass in
an integer that represents the largest random number you want In this case, you'll pass in the value 10 to generate a random number between 0 and 10:
Random r = new Random( );
// a simple class to store in the array
public class Employee : IComparable
// Comparer delegates back to Employee
// Employee uses the integer's default
ArrayList empArray = new ArrayList( );
ArrayList intArray = new ArrayList( );
// generate random numbers for
// both the integers and the
// employee id's
Random r = new Random( );
Trang 20// populate the array
for (int i = 0;i<5;i++)
// display all the contents of the int array
for (int i = 0;i<intArray.Count;i++)
{
Console.Write("{0} ", intArray[i].ToString( ));
}
Console.WriteLine("\n");
// display all the contents of the Employee array
for (int i = 0;i<empArray.Count;i++)
// display all the contents of the Employee array
for (int i = 0;i<empArray.Count;i++)
Trang 219.5.2 Implementing IComparer
When you call Sort( ) on the ArrayList the default implementation of IComparer is called, which uses QuickSort to call the IComparable implementation of CompareTo( ) on each element in the ArrayList
You are free to create your own implementation of IComparer, which you might want to do if you need control over how the sort is accomplished For example, in the next example, you will add a second field to Employee, yearsOfSvc You want to be able to sort the Employee
objects in the ArrayList on either field, empID or yearsOfSvc
To accomplish this, you will create a custom implementation of IComparer, which you will pass to the Sort( ) method of ArrayList This IComparer class, EmployeeComparer, knows about Employee objects and knows how to sort them
When you invoke Sort( ) the ArrayList will call the Compare method on the
EmployeeComparer, which in turn will delegate the comparison to the
Employee.CompareTo( ) method, passing in its WhichComparison property.
Trang 22public int Compare(object lhs, object rhs)
Example 9-14 Sorting an array by employees' IDs and years of service
// a simple class to store in the array
public class Employee : IComparable
Trang 23// static method to get a Comparer object
public static EmployeeComparer GetComparer( )
{
return new Employee.EmployeeComparer( );
}
// Comparer delegates back to Employee
// Employee uses the integer's default
// Special implementation to be called by custom comparer
public int CompareTo(
// nested class which implements IComparer
public class EmployeeComparer : IComparer
{
// enumeration of comparsion types
public enum ComparisonType
{
EmpID,
Yrs
};
// Tell the Employee objects to compare themselves
public int Compare(object lhs, object rhs)
Trang 24// private state variable
private Employee.EmployeeComparer.ComparisonType
whichComparison;
}
private int empID;
private int yearsOfSvc = 1;
ArrayList empArray = new ArrayList( );
// generate random numbers for
// both the integers and the
// employee id's
Random r = new Random( );
// populate the array
for (int i = 0;i<5;i++)
// display all the contents of the Employee array
for (int i = 0;i<empArray.Count;i++)
// display all the contents of the Employee array
for (int i = 0;i<empArray.Count;i++)
for (int i = 0;i<empArray.Count;i++)
Trang 25of service
9.6 Queues
A queue represents a first-in, first-out (FIFO) collection The classic analogy is to a line (or
queue if you are British) at a ticket window The first person in line ought to be the first person to come off the line to buy a ticket
A queue is a good collection to use when you are managing a limited resource For example, you might want to send messages to a resource that can only handle one message at a time You would then create a message queue so that you can say to your clients: "Your message is important to us Messages are handled in the order in which they are received."
The Queue class has a number of member methods and properties, as shown in Table 9-4
Table 9-4 Queue methods and properties Method or property Purpose
Synchronized() Public static method that returns a Queue wrapper that is thread-safe
Count Public property that gets the number of elements in the Queue
IsSynchronized Public property to get a value indicating if the Queue is synchronized
SyncRoot Public property that returns an object that can be used to synchronize access to the Queue Clear() Removes all objects from the Queue
Clone() Creates a shallow copy
Contains() Determines if an element is in the Queue
CopyTo() Copies the Queue elements to an existing one-dimensional array
Dequeue() Removes and returns the object at the beginning of the Queue
Enqueue() Adds an object to the end of the Queue
GetEnumerator() Returns an enumerator for the Queue
Trang 26Peek() Returns the object at the beginning of the Queue without removing it
ToArray() Copies the elements to a new array
Add elements to your queue with the Enqueue command and take them off the queue with
Dequeue or by using an enumerator Example 9-15 illustrates
Example 9-15 Working with a queue
Queue intQueuee = new Queue( );
// populate the array
for (int i = 0;i<5;i++)
{
intQueuee.Enqueue(i*5);
}
// Display the Queue
Console.Write( "intQueuee values:\t" );
PrintValues( intQueuee );
// Remove an element from the queue
Console.WriteLine(
"\n(Dequeue)\t{0}", intQueuee.Dequeue( ) );
// Display the Queue
Console.Write( "intQueuee values:\t" );
PrintValues( intQueuee );
// Remove another element from the queue
Console.WriteLine(
"\n(Dequeue)\t{0}", intQueuee.Dequeue( ) );
// Display the Queue
Console.Write( "intQueuee values:\t" );
PrintValues( intQueuee );
// View the first element in the
// Queue but do not remove
Console.WriteLine(
"\n(Peek) \t{0}", intQueuee.Peek( ) );
// Display the Queue
Console.Write( "intQueuee values:\t" );
PrintValues( intQueuee );
}
Trang 27public static void PrintValues( IEnumerable myCollection )
In this example the ArrayList is replaced by a Queue I've dispensed with the Employee class
to save room, but of course you can Enqueue user-defined objects as well
The output shows that queuing objects adds them to the Queue, and calls to Dequeue return the object and also remove them from the Queue The Queue class also provides a Peek( )
method that allows you to see, but not remove, the first element
Because the Queue class is enumerable, you can pass it to the PrintValues method, which is provided as an IEnumerable interface The conversion is implicit In the PrintValues
method you call GetEnumerator, which you will remember is the single method of all
IEnumerable classes This returns an IEnumerator, which you then use to enumerate all the objects in the collection
9.7 Stacks
A stack is a last-in, first-out (LIFO) collection, like a stack of dishes at a buffet table, or a
stack of coins on your desk Add a dish on top, which is the first dish you take off the stack
The principal methods for adding to and removing from a stack are Push and Pop( ); Stack
also offers a Peek( ) method, very much like Queue The significant methods and properties for Stack are shown in Table 9-5
Table 9-5 Stack methods and properties Method or property Purpose
Synchronized() Public static method that returns a thread-safe Stack wrapper
Count Public property that gets the number of elements in the Stack
IsSynchronized Public property that gets a value indicating if the Stack is synchronized
Trang 28SyncRoot Public property that returns an object that can be used to synchronize access to
the Stack
Clear() Removes all objects from the Stack
Clone() Creates a shallow copy
Contains() Determines if an element is in the Stack
CopyTo() Copies the Stack elements to an existing one-dimensional array
GetEnumerator() Returns an enumerator for the Stack
Peek() Returns the object at the top of the Stack without removing it
Pop() Removes and returns the object at the top of the Stack
Push() Inserts an object at the top of the Stack
ToArray() Copies the elements to a new array
The ArrayList, Queue, and Stack types contain overloaded CopyTo( ) and ToArray( )
methods for copying their elements to an array In the case of a Stack, the CopyTo( ) method will copy its elements to an existing one-dimensional array, overwriting the contents of the array beginning at the index you specify The ToArray( ) method returns a new array with the contents of the stack's elements Example 9-16 illustrates
Example 9-16 Working with a Stack
Stack intStack = new Stack( );
// populate the array
for (int i = 0;i<8;i++)
{
intStack.Push(i*5);
}
// Display the Stack
Console.Write( "intStack values:\t" );
PrintValues( intStack );
// Remove an element from the stack
Console.WriteLine( "\n(Pop)\t{0}",
intStack.Pop( ) );
// Display the Stack
Console.Write( "intStack values:\t" );
Trang 29// View the first element in the
// Stack but do not remove
Console.WriteLine( "\n(Peek) \t{0}",
intStack.Peek( ) );
// Display the Stack
Console.Write( "intStack values:\t" );
// Display the values of the target Array instance
Console.WriteLine( "\nTarget array: ");
PrintValues( targetArray );
// Copy the entire source Stack to the
// target Array instance, starting at index 6
intStack.CopyTo( targetArray, 6 );
// Display the values of the target Array instance
Console.WriteLine( "\nTarget array after copy: ");
PrintValues( targetArray );
// Copy the entire source Stack
// to a new standard array
Object[] myArray = intStack.ToArray( );
// Display the values of the new standard array
Console.WriteLine( "\nThe new array:" );