Notice that the overloaded indexer and the indexer from Example 9-9 are able // a simplified ListBox control public class ListBoxTest { private string[] strings; private int ctr = 0
Trang 1This code is kept simple and thus is not robust There are any number
of other checks you’ll want to make on the value passed in (e.g.,
checking that you were not passed a negative index, and that it doesn’t
exceed the size of the underlyingstrings[] array)
This allows you to create a “sparse” array in which you can assign to offset 10 out ever having assigned to offset 9 Thus, if you now write:
ListBoxTest lbt = new ListBoxTest("Hello", "World");
Then, callAdd( ) to add four more strings:
// add a few strings
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("Douglas");
lbt.Add("Adams");
Before examining the values, modify the second value (at index1):
string subst = "Universe";
lbt[1] = subst;
Finally, display each value in a loop:
for (int i = 0;i<lbt.GetNumEntries( );i++)
{
Console.WriteLine("lbt[{0}]: {1}",i,lbt[i]);
}
Trang 2Indexers | 183
Indexing on Other Values
C# doesn’t 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 your listbox, you might want to be able to index into the listbox based
on a string Example 9-10 illustrates a string index The indexer callsfindString( ),which is a helper method that returns a record based on the value of the string pro- vided Notice that the overloaded indexer and the indexer from Example 9-9 are able
// a simplified ListBox control
public class ListBoxTest
{
private string[] strings;
private int ctr = 0;
// initialize the listbox 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 listbox
public void Add(string theString)
{
strings[ctr] = theString;
ctr++;
}
// allow array-like access
public string this[int index]
{
Trang 3}
// publish how many strings you hold
public int GetNumEntries( )
Trang 4Indexers | 185
Example 9-10 is identical to Example 9-9 except for the addition of an overloaded indexer, which can match a string, and the method findString, created to supportthat index.
ThefindStringmethod simply iterates through the strings held inmyStringsuntil it finds a string that starts with the target string you use in the index If found, it returns the index of that string; otherwise, it returns the value-1
We see inMain( )that the user passes in a string segment to the index, just as with an integer:
new ListBoxTest("Hello", "World");
// add a few strings
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("Douglas");
lbt.Add("Adams");
// 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++)
Trang 5This calls the overloaded index, which does some rudimentary error-checking (in this case, making sure the string passed in has at least one letter), and then passes the value (Hel) to findString It gets back an index and uses that index to index intomyStrings:
return this[findString(index)];
Theset value works in the same way:
myStrings[findString(index)] = value;
The careful reader will note that if the string doesn’t match, a value of
-1 is returned, which is then used as an index intomyStrings This
action then generates an exception (System.NullReferenceException),
as you can see by uncommenting the following line inMain( ):
lbt["xyz"] = "oops";
The proper handling of not finding a string is, as they say, left as an
exercise for the reader You might consider displaying an error
mes-sage or otherwise allowing the user to recover from the error
Collection Interfaces
The NET Framework provides two sets of standard interfaces for enumerating and comparing collections: the traditional (nontype-safe) and the new generic type-safe collections This book focuses only on the new type-safe collection interfaces, as these are far preferable.
You can declare anICollectionof any specific type by substituting the actual type
(e.g.,int orstring) for the generic type in the interface declaration (<T>)
C++ programmers take note: C# Generics are similar in syntax and
usage to C++ templates However, because the generic types are
expanded to their specific type at runtime, the JIT compiler is able to
share code among different instances, dramatically reducing the code
bloat that you may see when using templates in C++
Table 9-2 lists the key generic collection interfaces.*
* For backward compatibility, C# also provides nongeneric interfaces (e.g.,ICollection,IEnumerator), butthey aren’t considered here because they are obsolete
Table 9-2 Collection interfaces
Trang 6Collection Interfaces | 187
The IEnumerable<T> Interface
You can support the foreach statement in ListBoxTest by implementing theIEnumerable<T> interface (see Example 9-11).IEnumerable<T> has only one method,GetEnumerator( ), whose job is to return an implementation ofIEnumerator<T> TheC# language provides special help in creating the enumerator, using the new key- wordyield
ICollection<T> Implemented by all collections to provide theCopyTo( ) method as well as theCount,
IsSynchronized, andSyncRoot propertiesIComparer<T>
IComparable<T>
Compare two objects held in a collection so that the collection can be sortedIList<T> Used by array-indexable collections
IDictionary<K,V> Used for key-/value-based collections such asDictionary
Example 9-11 Making a ListBox an enumerable class
// Enumerable classes can return an enumerator
public IEnumerator<string> GetEnumerator( )
// initialize the listbox with strings
public ListBoxTest(params string[] initialStrings)
{
// allocate space for the strings
strings = new String[8];
Table 9-2 Collection interfaces (continued)
Trang 7// copy the strings passed in to the constructor foreach (string s in initialStrings)
// 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");
Example 9-11 Making a ListBox an enumerable class (continued)
Trang 8Collection Interfaces | 189
The program begins inMain( ), creating a newListBoxTest object and passing two strings to the constructor When the object is created, an array ofStrings is createdwith enough room for eight strings Four more strings are added using the Addmethod, and the second string is updated, just as in the previous example.
The big change in this version of the program is that aforeachloop is called, ing each string in the listbox Theforeachloop automatically uses theIEnumerable<T>interface, invokingGetEnumerator( )
retriev-TheGetEnumerator method is declared to return anIEnumerator of string:
public IEnumerator<string> GetEnumerator( )
The implementation iterates through the array of strings, yielding each in turn:foreach ( string s in strings )
// test the access
string subst = "Universe";
Trang 9IComparable<Node<T>> where T : IComparable<T>
This defines a generic Node that holds a type, T Node of T implements theIComparable<T>interface, which means that twoNodes ofTcan be compared TheNodeclass is constrained (where T : IComparable<T>) to hold only types that implement theIComparable interface Thus, you may substitute any type forT as long as that type implementsIComparable
Example 9-12 illustrates the complete implementation, with analysis to follow.
Example 9-12 Using constraints
private string name;
public Employee(string name)
// implement the interface
public int CompareTo(Employee rhs)
// node must implement IComparable of Node of T
// constrain Nodes to only take items that implement IComparable
// by using the where keyword
Trang 10Constraints | 191
public class Node<T> :
IComparable<Node<T>> where T : IComparable<T>
{
// member fields
private T data;
private Node<T> next = null;
private Node<T> prev = null;
public T Data { get { return this.data; } }
public Node<T> Next
newNode.next = this; // new node points to me
// if I have a previous, set it to point to
// the new node as its next
Trang 11// return the newNode in case it is the new head return newNode;
Node<T> node = headNode;
Example 9-12 Using constraints (continued)
Trang 13In this example, you begin by declaring a class that can be placed into the linked list:public class Employee : IComparable<Employee>
This declaration indicates thatEmployeeobjects are comparable, and you see that theEmployeeclass implements the required methods (CompareTo andEquals) Note thatthese methods are type-safe (they know that the parameter passed to them will be of type Employee) TheLinkedListitself is declared to hold only types that implementIComparable:
public class LinkedList<T> where T : IComparable<T>
so you are guaranteed to be able to sort the list TheLinkedListholds an object of typeNode.Nodealso implementsIComparableand requires that the objects it holds as data themselves implementIComparable:
public class Node<T> :
IComparable<Node<T>> where T : IComparable<T>
These constraints make it safe and simple to implement theCompareTomethod ofNodebecause theNode knows it will be comparing otherNodes whose data is comparable:
// make an instance, run the method
Test t = new Test( );
t.Run( );
}
public void Run( )
{
LinkedList<int> myLinkedList = new LinkedList<int>( );
Random rand = new Random( );
Trang 14Your 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 List class is an array whose size is dynamically increased as required. Listsprovide a number of useful methods and properties for their manipulation Table 9-3 shows some of the most important ones.
Table 9-3 List methods and properties
Method or property Purpose
Capacity Property to get or set the number of elements theList can contain; this value is increased
auto-matically if count exceeds capacity; you might set this value to reduce the number of reallocations,and you may callTrim( ) to reduce this value to the actualCount
Count Property to get the number of elements currently in the array
Item( ) Gets or sets the element at the specified index; this is the indexer for theList classa
Add( ) Public method to add an object to theList
AddRange( ) Public method that adds the elements of anICollection to the end of theList
AsReadOnly( ) Public method that returns a read-only instance of the current instance
BinarySearch( ) Overloaded public method that uses a binary search to locate a specific element in a sortedListClear( ) Removes all elements from theList
Contains( ) Determines whether an element is in theList
ConvertAll( ) Public method that converts all elements in the current list into another type
CopyTo( ) Overloaded public method that copies aList to a one-dimensional array
Exists( ) Determines whether an element is in theList
Find( ) Returns the first occurrence of the element in theList
FindAll( ) Returns all the specified elements in theList
FindIndex( ) Overloaded public method that returns the index of the first element that matches a conditionFindLast( ) Public method that finds the last element that matches a condition
Trang 15When you create aList, you don’t define how many objects it will contain You add
to theListusing theAdd( )method, and the list takes care of its own internal keeping, as illustrated in Example 9-13.
book-FindLastIndex( ) Overloaded public method that returns the index of the last element that matches a conditionForEach( ) Public static method that performs an action on all elements of an array
GetEnumerator( ) Overloaded public method that returns an enumerator to iterate through aList
GetRange( ) Copies a range of elements to a newList
IndexOf( ) Overloaded public method that returns the index of the first occurrence of a value
Insert( ) Inserts an element into theList
InsertRange( ) Inserts the elements of a collection into theList
LastIndexOf( ) Overloaded public method that returns the index of the last occurrence of a value in theListRemove( ) Removes the first occurrence of a specific object
RemoveAll( ) Removes all elements that match a specific condition
RemoveAt( ) Removes the element at the specified index
RemoveRange( ) Removes a range of elements
Reverse( ) Reverses the order of elements in theList
Sort( ) Sorts theList
ToArray( ) Copies the elements of theList to a new array
TrimExcess( ) Reduce the current list’s capacity to the actual number of elements in the list
TrimToSize( ) Sets the capacity of the actual number of elements in theList
a The idiom in the FCL is to provide anItem element for collection classes that is implemented as an indexer in C#
Example 9-13 Working with List
// a simple class to store in the List
public class Employee
public int EmpID { get; set; }
Table 9-3 List methods and properties (continued)
Method or property Purpose
Trang 16List<T> | 197
With anArray class, you define how many objects the array will hold If you try to add more than that, theArrayclass will throw an exception With aList, you don’tdeclare how many objects the List will hold The List has a property, Capacity,which is the number of elements that theList is capable of storing:
public int Capacity { get; set; }
The default capacity is eight When you add the 17th element, the capacity is matically doubled to 16 If you change thefor loop to:
auto-for (int i = 0;i < 9;i++)
List<Employee> empList = new List<Employee>( );
List<int> intList = new List<int>( );
// populate the List
for (int i = 0; i < 5; i++)
{
empList.Add(new Employee(i + 100));
intList.Add(i * 5);
}
// print all the contents
for (int i = 0; i < intList.Count; i++)
{
Console.Write("{0} ", intList[i].ToString( ));
}
Console.WriteLine("\n");
// print all the contents of the Employee List
for (int i = 0; i < empList.Count; i++)
Trang 17the 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 typeArgumentOutOfRangeException
Implementing IComparable
Like all collections, the List implements the Sort( )method, which allows you to sort any objects that implementIComparable In the next example, you’ll modify theEmployee object to implementIComparable:
public class Employee : IComparable<Employee>
To implement the IComparable<Employee> interface, the Employee object must vide aCompareTo( ) method:
pro-public int CompareTo(Employee rhs)
The System.Int32class implements IComparable<Int32>, so you may
delegate the comparison responsibility to integers
You are now ready to sort the array list of employees,empList To see whether thesort is working, you’ll need to add integers andEmployeeinstances to their respective arrays with random values To create the random values, you’ll instantiate an object
of classRandom; to generate the random values, you’ll call theNext( )method on theRandomobject, which returns a pseudorandom number TheNext( )method is over- loaded; 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 value10to generate a ran- dom number between0 and10:
Random r = new Random( );
r.Next(10);
Trang 18List<T> | 199
Example 9-14 creates an integer array and an Employeearray, populates them both with random numbers, and prints their values It then sorts both arrays and prints the new values.
Example 9-14 Sorting an integer and an employee array
// a simple class to store in the array
public class Employee : IComparable<Employee>
{
private int empID;
public Employee(int empID)
// Comparer delegates back to Employee
// Employee uses the integer's default
List<Employee> empArray = new List<Employee>( );
List<Int32> intArray = new List<Int32>( );
Trang 19// generate random numbers for
// both the integers and the
// employee IDs
Random r = new Random( );
// populate the array
for (int i = 0; i < 5; i++)
{
// add a random employee id
empArray.Add(new Employee(r.Next(10) + 100)); // add a random integer
Trang 20You are free to create your own implementation ofIComparer, which you might want
to do if you need control over how the sort ordering is defined In the next example, you will add a second field toEmployee,yearsOfSvc You want to be able to sort theEmployee objects in theList on either field—empID oryearsOfSvc
To accomplish this, create a custom implementation of IComparerthat you pass to theSort( )method of theList ThisIComparerclass,EmployeeComparer, knows aboutEmployee objects and knows how to sort them
EmployeeComparer has the WhichComparison property, of type Employee.EmployeeComparer.ComparisonType:
Trang 21When you invokeSort( ), theListcalls theComparemethod on theEmployeeComparer,which in turn delegates the comparison to theEmployee.CompareTo( )method, passing
in itsWhichComparison property:
public int Compare( Employee lhs, Employee rhs )
Example 9-15 Sorting an array by employees’ IDs and years of service
private int empID;
private int yearsOfSvc = 1;
public Employee(int empID)
Trang 22// 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(Employee rhs,
// nested class which implements IComparer
public class EmployeeComparer : IComparer<Employee>
{
// enumeration of comparison types
public enum ComparisonType
Example 9-15 Sorting an array by employees’ IDs and years of service (continued)
Trang 23// Tell the Employee objects to compare themselves
public int Compare(Employee lhs, Employee rhs)
List<Employee> empArray = new List<Employee>( );
// generate random numbers for
// both the integers and the
// employee IDs
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++)
{
Console.Write("\n{0} ", empArray[i].ToString( ));
Example 9-15 Sorting an array by employees’ IDs and years of service (continued)
Trang 24List<T> | 205
The first block of output shows theEmployeeobjects as they are added to theList.The employee ID values and the years of service are in random order The second block shows the results of sorting by the employee ID, and the third block shows the results of sorting by years of service.
// display all the contents of the Employee array
for (int i = 0; i < empArray.Count; i++)
Trang 25If you are creating your own collection, as in Example 9-11, and wish
to implementIComparer, you may need to ensure that all the types
placed in the list implementIComparer(so that they may be sorted), by
using constraints, as described earlier Note that in a production
envi-ronment, employee ID would always be nonrandom and unique
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 handle only 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.
You add elements to your queue with theEnqueuecommand, and take them off the queue withDequeue or by using an enumerator Example 9-16 illustrates
Table 9-4 Queue methods and properties
Method or property Purpose
Count Public property that gets the number of elements in theQueue
Clear( ) Removes all objects from theQueue
Contains( ) Determines whether an element is in theQueue
CopyTo( ) Copies theQueue elements to an existing one-dimensional array
Dequeue( ) Removes and returns the object at the beginning of theQueue
Enqueue( ) Adds an object to the end of theQueue
GetEnumerator( ) Returns an enumerator for theQueue
Peek( ) Returns the object at the beginning of theQueue without removing it
ToArray( ) Copies the elements to a new array
TrimExcess( ) Reduces the current queue’s capacity to the actual number of elements in the list
Example 9-16 Working with a queue
using System;
using System.Collections.Generic;
using System.Text;