Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp And The .NET 2.0 Platform (2005) [eng]

.pdf
Скачиваний:
110
Добавлен:
16.08.2013
Размер:
10.35 Mб
Скачать

244 C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

{

Car temp = (Car)obj; if(this.carID > temp.carID)

return 1; if(this.carID < temp.carID)

return -1;

else

return 0;

}

}

As you can see, the logic behind CompareTo() is to test the incoming type against the current instance based on a specific point of data. The return value of CompareTo() is used to discover if this type is less than, greater than, or equal to the object it is being compared with (see Table 7-1).

Table 7-1. CompareTo() Return Values

CompareTo() Return Value

Meaning in Life

Any number less than zero

This instance comes before the specified object in the sort order.

Zero

This instance is equal to the specified object.

Any number greater than zero

This instance comes after the specified object in the sort order.

 

 

Now that your Car type understands how to compare itself to like objects, you can write the following user code:

// Exercise the IComparable interface. static void Main(string[] args)

{

// Make an array of Car types.

...

// Dump current array.

Console.WriteLine("Here is the unordered set of cars:"); foreach(Car c in myAutos)

Console.WriteLine("{0} {1}", c.ID, c.PetName);

//Now, sort them using IComparable!

Array.Sort(myAutos);

//Dump sorted array.

Console.WriteLine("Here is the ordered set of cars:"); foreach(Car c in myAutos)

Console.WriteLine("{0} {1}", c.ID, c.PetName); Console.ReadLine();

}

Figure 7-10 illustrates a test run.

C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

245

Figure 7-10. Comparing automobiles based on car ID

Specifying Multiple Sort Orders (IComparer)

In this version of the Car type, you made use of the car’s ID to function as the baseline of the sort order. Another design might have used the pet name of the car as the basis of the sorting algorithm (to list cars alphabetically). Now, what if you wanted to build a Car that could be sorted by ID as well as by pet name? If this is the behavior you are interested in, you need to make friends with another standard interface named IComparer, defined within the System.Collections namespace as follows:

// A generic way to compare two objects. interface IComparer

{

int Compare(object o1, object o2);

}

Unlike the IComparable interface, IComparer is typically not implemented on the type you are trying to sort (i.e., the Car). Rather, you implement this interface on any number of helper classes, one for each sort order (pet name, car ID, etc.). Currently, the Car type already knows how to compare itself against other cars based on the internal car ID. Therefore, to allow the object user to sort an array of Car types by pet name will require an additional helper class that implements IComparer. Here’s the code:

// This helper class is used to sort an array of Cars by pet name. using System.Collections;

public class PetNameComparer : IComparer

{

public PetNameComparer(){ }

// Test the pet name of each object.

int IComparer.Compare(object o1, object o2)

{

Car t1 = (Car)o1;

Car t2 = (Car)o2;

return String.Compare(t1.PetName, t2.PetName);

}

}

246 C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

The object user code is able to make use of this helper class. System.Array has a number of overloaded Sort() methods, one that just happens to take an object implementing IComparer (see Figure 7-11):

static void Main(string[] args)

{

...

//Now sort by pet name.

Array.Sort(myAutos, new PetNameComparer());

//Dump sorted array.

Console.WriteLine("Ordering by pet name:"); foreach(Car c in myAutos)

Console.WriteLine("{0} {1}", c.ID, c.PetName);

...

}

Figure 7-11. Sorting automobiles by pet name

Custom Properties, Custom Sort Types

It is worth pointing out that you can make use of a custom static property in order to help the object user along when sorting your Car types by a specific data point. Assume the Car class has added

a static read-only property named SortByPetName() that returns an instance of an object implementing the IComparer interface (PetNameComparer, in this case):

//We now support a custom property to return

//the correct IComparer interface.

public class Car : IComparable

{

...

// Property to return the SortByPetName comparer. public static IComparer SortByPetName

{ get { return (IComparer)new PetNameComparer(); } }

}

C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

247

The object user code can now sort by pet name using a strongly associated property, rather than just “having to know” to use the stand-alone PetNameComparer class type:

// Sorting by pet name made a bit cleaner.

Array.Sort(myAutos, Car.SortByPetName);

Source Code The ComparableCar project is located under the Chapter 7 subdirectory.

Hopefully at this point, you not only understand how to define and implement interface types, but can understand their usefulness. To be sure, interfaces will be found within every major .NET namespace. To wrap up this chapter, let’s check out the interfaces (and core classes) of the

System.Collections namespace.

The Interfaces of the System.Collections Namespace

The most primitive container construct would have to be our good friend System.Array. As you have already seen in Chapter 3, this class provides a number of services (e.g., reversing, sorting, clearing, and enumerating). However, the simple Array class has a number of limitations, most notably it does not dynamically resize itself as you add or clear items. When you need to contain types in a more flexible container, you may wish to leverage the types defined within the System.Collections namespace (or as discussed in Chapter 10, the System.Collections.Generic namespace).

The System.Collections namespace defines a number of interfaces (some of which you have already implemented during the course of this chapter). As you can guess, a majority of the collection classes implement these interfaces to provide access to their contents. Table 7-2 gives a breakdown of the core collection-centric interfaces.

Table 7-2. Interfaces of System.Collections

System.Collections Interface

Meaning in Life

ICollection

Defines generic characteristics (e.g., count and thread safety) for

 

a collection type.

IComparer

Allows two objects to be compared.

IDictionary

Allows an object to represent its contents using name/value pairs.

IDictionaryEnumerator

Enumerates the contents of a type supporting IDictionary.

IEnumerable

Returns the IEnumerator interface for a given object.

IEnumerator

Generally supports foreach-style iteration of subtypes.

IHashCodeProvider

Returns the hash code for the implementing type using

 

a customized hash algorithm.

IKeyComparer

(This interface is new to .NET 2.0.) Combines the functionality

 

of IComparer and IHashCodeProvider to allow objects to be

 

compared in a “hash-code-compatible manner” (e.g., if the

 

objects are indeed equal, they must also return the same hash

 

code value).

IList

Provides behavior to add, remove, and index items in a list of

 

objects. Also, this interface defines members to determine

 

whether the implementing collection type is read-only and/or

 

a fixed-size container.

 

 

248 C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

Many of these interfaces are related by an interface hierarchy, while others are stand-alone entities. Figure 7-12 illustrates the relationship between each type (recall that it is permissible for a single interface to derive from multiple interfaces).

Figure 7-12. The System.Collections interface hierarchy

The Role of ICollection

The ICollection interface is the most primitive interface of the System.Collections namespace in that it defines a behavior supported by a collection type. In a nutshell, this interface provides a small set of properties that allow you to determine (a) the number of items in the container,

(b) the thread safety of the container, as well as (c) the ability to copy the contents into a System.Array type. Formally, ICollection is defined as follows (note that ICollection extends IEnumerable):

public interface ICollection : IEnumerable

{

// IEnumerable member...

int Count { get; }

bool IsSynchronized { get; } object SyncRoot { get; }

void CopyTo(Array array, int index);

}

The Role of IDictionary

As you may already be aware, a dictionary is simply a collection that maintains a set of name/value pairs. For example, you could build a custom type that implements IDictionary such that you can store Car types (the values) that may be retrieved by ID or pet name (e.g., names). Given this functionality, you can see that the IDictionary interface defines a Keys and Values property as well as Add(), Remove(), and Contains() methods. The individual items may be obtained by the type indexer. Here is the formal definition:

public interface IDictionary : ICollection, IEnumerable

{

C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

249

bool IsFixedSize { get; } bool IsReadOnly { get; }

object this[ object key ] { get; set; } ICollection Keys { get; }

ICollection Values { get; }

void Add(object key, object value); void Clear();

bool Contains(object key); IDictionaryEnumerator GetEnumerator(); void Remove(object key);

}

The Role of IDictionaryEnumerator

If you were paying attention, you may have noted that IDictionary.GetEnumerator() returns an instance of the IDictionaryEnumerator type. IDictionaryEnumerator is simply a strongly typed enumerator, given that it extends IEnumerator by adding the following functionality:

public interface IDictionaryEnumerator : IEnumerator

{

// IEnumerator methods...

DictionaryEntry Entry { get; } object Key { get; }

object Value { get; }

}

Notice how IDictionaryEnumerator allows you to enumerate over items in the dictionary via the generic Entry property, which returns a System.Collections.DictionaryEntry class type. In addition, you are also able to traverse the name/value pairs using the Key/Value properties.

The Role of IList

The final key interface of System.Collections is IList, which provides the ability to insert, remove, and index items into (or out of) a container:

public interface IList : ICollection, IEnumerable

{

bool IsFixedSize { get; } bool IsReadOnly { get; }

object this[ int index ] { get; set; } int Add(object value);

void Clear();

bool Contains(object value); int IndexOf(object value);

void Insert(int index, object value); void Remove(object value);

void RemoveAt(int index);

}

The Class Types of System.Collections

As I hope you understand by this point in the chapter, interfaces by themselves are not very useful until they are implemented by a given class or structure. Table 7-3 provides a rundown of the core classes in the System.Collections namespace and the key interfaces they support.

250 C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

Table 7-3. Classes of System.Collections

System.Collections Class

Meaning in Life

Key Implemented Interfaces

ArrayList

Represents a dynamically sized

IList, ICollection,

 

array of objects.

IEnumerable, and ICloneable

Hashtable

Represents a collection of objects

IDictionary, ICollection,

 

identified by a numerical key.

IEnumerable, and ICloneable

 

Custom types stored in a Hashtable

 

 

should always override

 

 

System.Object.GetHashCode().

 

Queue

Represents a standard first-in,

ICollection, ICloneable, and

 

first-out (FIFO) queue.

IEnumerable

SortedList

Like a dictionary; however, the

IDictionary, ICollection,

 

elements can also be accessed by

IEnumerable, and ICloneable

 

ordinal position (e.g., index).

 

Stack

A last-in, first-out (LIFO) queue

ICollection, ICloneable, and

 

providing push and pop (and peek)

IEnumerable

 

functionality.

 

 

 

 

In addition to these key types, System.Collections defines some minor players (at least in terms of their day-to-day usefulness) such as BitArray, CaseInsensitiveComparer, and CaseInsensitiveHashCodeProvider. Furthermore, this namespace also defines a small set of abstract base classes (CollectionBase, ReadOnlyCollectionBase, and DictionaryBase) that can be used to build strongly typed containers.

As you begin to experiment with the System.Collections types, you will find they all tend to share common functionality (that’s the point of interface-based programming). Thus, rather than listing out the members of each and every collection class, the next task of this chapter is to illustrate how to interact with three common collection types: ArrayList, Queue, and Stack. Once you understand the functionality of these types, gaining an understanding of the remaining collection classes should naturally follow (especially since each of the types is fully documented within online help).

Working with the ArrayList Type

The ArrayList type is bound to be your most frequently used type in the System.Collections namespace in that it allows you to dynamically resize the contents at your whim. To illustrate the basics of this type, ponder the following code, which leverages the ArrayList to manipulate a set of Car objects:

static void Main(string[] args)

{

//Create ArrayList and fill with some initial values.

ArrayList carArList = new ArrayList(); carArList.AddRange(new Car[] { new Car("Fred", 90, 10),

new Car("Mary", 100, 50), new Car("MB", 190, 11)}); Console.WriteLine("Items in carArList: {0}", carArList.Count);

//Print out current values.

foreach(Car c in carArList)

Console.WriteLine("Car pet name: {0}", c.PetName);

// Insert a new item.

Console.WriteLine("\n->Inserting new Car."); carArList.Insert(2, new Car("TheNewCar", 0, 12)); Console.WriteLine("Items in carArList: {0}", carArList.Count);

C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

251

// Get object array from ArrayList and print again. object[] arrayOfCars = carArList.ToArray(); for(int i = 0; i < arrayOfCars.Length; i++)

{

Console.WriteLine("Car pet name: {0}", ((Car)arrayOfCars[i]).PetName);

}

}

Here you are making use of the AddRange() method to populate your ArrayList with a set of Car types (as you can tell, this is basically a shorthand notation for calling Add() n number of times). Once you print out the number of items in the collection (as well as enumerate over each item to obtain the pet name), you invoke Insert(). As you can see, Insert() allows you to plug a new item into the ArrayList at a specified index. Finally, notice the call to the ToArray() method, which returns a generic array of System.Object types based on the contents of the original ArrayList. Figure 7-13 shows the output.

Figure 7-13. Fun with System.Collections.ArrayList

Working with the Queue Type

Queues are containers that ensure items are accessed using a first-in, first-out manner. Sadly, we humans are subject to queues all day long: lines at the bank, lines at the movie theater, and lines at the morning coffeehouse. When you are modeling a scenario in which items are handled on a first-come, first-served basis, System.Collections.Queue is your type of choice. In addition to the functionality provided by the supported interfaces, Queue defines the key members shown in Table 7-4.

Table 7-4. Members of the Queue Type

Member of System.Collection.Queue

Meaning in Life

Dequeue()

Removes and returns the object at the beginning of the

 

Queue

Enqueue()

Adds an object to the end of the Queue

Peek()

Returns the object at the beginning of the Queue without

 

removing it

 

 

252 C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

To illustrate these methods, we will leverage our automobile theme once again and build a Queue object that simulates a line of cars waiting to enter a car wash. First, assume the following static helper method:

public static void WashCar(Car c)

{

Console.WriteLine("Cleaning {0}", c.PetName);

}

Now, ponder the following code:

static void Main(string[] args)

{

...

//Make a Q with three items.

Queue carWashQ = new Queue(); carWashQ.Enqueue(new Car("FirstCar", 0, 1)); carWashQ.Enqueue(new Car("SecondCar", 0, 2)); carWashQ.Enqueue(new Car("ThirdCar", 0, 3));

//Peek at first car in Q.

Console.WriteLine("First in Q is {0}", ((Car)carWashQ.Peek()).PetName);

//Remove each item from Q.

WashCar((Car)carWashQ.Dequeue());

WashCar((Car)carWashQ.Dequeue());

WashCar((Car)carWashQ.Dequeue());

//Try to de-Q again?

try

{WashCar((Car)carWashQ.Dequeue()); } catch(Exception e)

{Console.WriteLine("Error!! {0}", e.Message);}

}

Here, you insert three items into the Queue type via its Enqueue() method. The call to Peek() allows you to view (but not remove) the first item currently in the Queue, which in this case is the car named FirstCar. Finally, the call to Dequeue() removes the item from the line and sends it into the WashCar() helper function for processing. Do note that if you attempt to remove items from an empty queue, a runtime exception is thrown.

Working with the Stack Type

The System.Collections.Stack type represents a collection that maintains items using a last-in, first-out manner. As you would expect, Stack defines a member named Push() and Pop() (to place items onto or remove items from the stack). The following stack example makes use of the standard

System.String:

static void Main(string[] args)

{

...

Stack stringStack = new Stack(); stringStack.Push("One"); stringStack.Push("Two"); stringStack.Push("Three");

C H A P T E R 7 I N T E R FA C E S A N D C O L L E C T I O N S

253

// Now look at the top item, pop it, and look again.

Console.WriteLine("Top item is: {0}", stringStack.Peek()); Console.WriteLine("Popped off {0}", stringStack.Pop()); Console.WriteLine("Top item is: {0}", stringStack.Peek()); Console.WriteLine("Popped off {0}", stringStack.Pop()); Console.WriteLine("Top item is: {0}", stringStack.Peek()); Console.WriteLine("Popped off {0}", stringStack.Pop());

try

{

Console.WriteLine("Top item is: {0}", stringStack.Peek()); Console.WriteLine("Popped off {0}", stringStack.Pop());

}

catch(Exception e)

{ Console.WriteLine("Error!! {0}", e.Message);}

}

Here, you build a stack that contains three string types (named according to their order of insertion). As you peek onto the stack, you will always see the item at the very top, and therefore the first call to Peek() reveals the third string. After a series of Pop() and Peek() calls, the stack is eventually empty, at which time additional Peek()/Pop() calls raise a system exception.

Source Code The CollectionTypes project can be found under the Chapter 7 subdirectory.

System.Collections.Specialized Namespace

In addition to the types defined within the System.Collections namespace, you should also be aware that the .NET base class libraries provide the System.Collections.Specialized namespace, which defines another set of types that are more (pardon the redundancy) specialized. For example, the StringDictionary and ListDictionary types each provide a stylized implementation of the IDictionary interface. Table 7-5 documents the key class types.

Table 7-5. Types of the System.Collections.Specialized Namespace

Member of System.Collections.Specialized

Meaning in Life

CollectionsUtil

Creates collections that ignore the case in strings.

HybridDictionary

Implements IDictionary by using

 

a ListDictionary while the collection is small,

 

and then switching to a Hashtable when the

 

collection gets large.

ListDictionary

Implements IDictionary using a singly linked list.

 

Recommended for collections that typically

 

contain ten items or fewer.

NameValueCollection

Represents a sorted collection of associated

 

String keys and String values that can be

 

accessed either with the key or with the index.

StringCollection

Represents a collection of strings.

StringDictionary

Implements a hashtable with the key strongly

 

typed to be a string rather than an object.

StringEnumerator

Supports a simple iteration over a StringCollection.