Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
(ebook) Visual Studio .NET Mastering Visual Basic.pdf
Скачиваний:
120
Добавлен:
17.08.2013
Размер:
15.38 Mб
Скачать

508 Chapter 11 STORING DATA IN COLLECTIONS

custom comparers in discussed in detail shortly in the section “The IEnumerator and IComparer Interfaces.” In the last example of that section, you will build a custom comparer for sorting the SortedList based on a function of its keys, instead of the actual values of the keys.

Remember the WordFrequencies project we built earlier to demonstrate the use of the HashTable class? Change the declaration of the WordFrequencies variable from HashTable to SortedList, and the project will work as before. The only difference is that the words will appear on the TextBox control sorted alphabetically when you click the Show Word Count button.

Other Collections

The System.Collections class exposes a few more collections, including the Queue and the Stack collections. The main characteristic of these two collections is how you add and remove items to them. When you add items to a Queue, the items are appended to the collection. When you remove items, they’re removed from the top of the collection. You’d use this collection to emulate the customer line in a bank or a production line.

The Stack collection inserts new items at the top, and you can only remove the top item. The Stack collection is a FIFO (first in first out) structure, while the Queue class is a LIFO structure (last in first out). You’d use this collection to emulate the stack maintained by the CPU, one of the most crucial structures for the operating system and applications alike. Stacks and Queues are used heavily in computer science, but they aren’t as common in business applications. I’m not going to discuss any more collections in this book, but you can look them up in the documentation. There are quite a few more interesting topics to cover in this chapter—and most important is how to save a collection to a disk file and read it back.

The IEnumerator and IComparer Interfaces

Judging by its title, you probably thought this is a section for C++ programmers adapted for VB programmers. IEnumerator and IComparer are two objects that unlock some of the most powerful features of collections. The proper term for IEnumerator and IComparer is interface, a term I will describe shortly. If you don’t want to get too technical about interfaces, think of them as objects. The IEnumerator object retrieves a list of pointers for all the items in a collection, and you can use it to iterate through the items in a collection. Every collection has a built-in enumerator, and you can retrieve it by calling its GetEnumerator method. The IComparer object exposes the Compare and CompareTo methods, which tells the compiler how to compare two objects of the same type. Once the compiler knows how to compare the objects, it can sort a collection of objects with the same type.

The IComparer interface consists of a function that compares two items and returns a value indicating their order (which one is the smaller item, or whether they’re equal). The Framework can’t compare objects of all types. It only knows how to compare the base types—integers, strings, and so on. It doesn’t know how to compare two rectangles, or two color objects. If you have a collection of colors, you may want to sort them according to their luminance, saturation, brightness, and so on. The compiler can’t make any assumptions as to how you may wish to sort your collection, and, of course, it doesn’t expose members to sort a collection in all possible ways. Instead, it gives you the option to specify a function that compares two colors (or two objects of any other type, for that matter) and uses this function to sort the collection. The same function is used by the BinarySearch method, to

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE IENUMERATOR AND ICOMPARER INTERFACES 509

locate an item in a sorted collection. In effect, the IComparer interface is a function that knows how to compare two Color objects, for our example. If the collection contains items of a custom Structure, the IComparer interface is a function that knows how to compare two instances of the custom Structure.

So, what is an interface? An interface is another term in object-oriented programming and describes a very simple technique. When we write the code for a class, we may not know how to implement a few operations, but we do know that they’ll have to be implemented later. We insert a placeholder for these operations (a function declaration) and expect that the application that uses the class will provide the actual implementation of these functions. All collections expose a Sort method, which sorts the items in the collection by comparing them to one another. To do so, the Sort method calls a function that compares two items and returns a value indicating their relative order. Any class that exposes a function that can compare its objects can be sorted. The Integer data type, which is implemented by the System.Integer class, exposes such a function, and so do all the base types. Custom objects must provide their own comparison function—or more than a single function, if you want to sort them in multiple ways. Since you can’t edit the collection’s Sort method’s code, you must supply your comparison function through a mechanism that the class can understand. This is what the IComparer interface is all about. The code that compares two objects of the same type is actually trivial. You must follow the steps outlined here to make this function part of the class, so that the collection can see and use it.

Enumerating Collections

All collections expose the IEnumerator interface, which is a fancy term for a very simple operation. IEnumerator returns an object that allows you to iterate through the collection without having to know anything about its items, not even the count of the items. To retrieve the enumerator for a collection, call its GetEnumerator method, with a statement like the following:

Dim ALEnum As IEnumerator

ALEnum = AList.GetEnumerator

The IEnumerator object exposes two methods, the MoveNext and Reset methods. The MoveNext method moves to the next item in the collection and makes it the current item. When you initialize the IEnumerator object, it’s positioned in front of the very first item, so you must call the MoveNext method to move to the first item. The Reset method does exactly the same: it repositions the IEnumerator in front of the first element.

The MoveNext method doesn’t return an item, as you might expect. It returns a True/False value indicating whether it has successfully moved to the next item. Once you have reached the end of the collection, the MoveNext method will return False. Here’s how you can enumerate through an ArrayList collection with an enumerator:

Dim aItems As IEnumerator aItems = aList.GetEnumerator While aItems.MoveNext

{ process item aItems.Current } End While

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

510 Chapter 11 STORING DATA IN COLLECTIONS

At each iteration, the current item is given by the Current property of the enumerator, which represents the current object in the collection. Once you have reached the last item, the MoveNext method will return False and the loop will terminate. To rescan the items, you must reset the enumerator by calling its Reset method.

To process the current item, you can directly call its methods through the aItems.Current object. If the collection holds Rectangles, for example, you can access their sizes with these expressions:

CType(aItems.Current, Rectangle).Width

CType(aItems.Current, Rectangle).Height

The Strict option necessitates the explicit conversion of the Current item to a Rectangle object. In other words, you can’t use an expression like aItems.Current.Width with the Strict option on.

The event handler in Listing 11.12 populates an ArrayList with Rectangle objects and then iterates through the collection and prints the area of each Rectangle.

Listing 11.12: Iterating an ArrayList with an Enumerator

Protected Sub Button2_Click(ByVal sender As Object, _ ByVal e As System.EventArgs)

Dim aList As New ArrayList()

Dim R1 As New Rectangle(1, 1, 10, 10) aList.Add(R1)

R1 = New Rectangle(2, 2, 20, 20) aList.Add(R1)

aList.add(New Rectangle(3, 3, 2, 2)) Dim REnum As IEnumerator

REnum = aList.GetEnumerator Dim R As New Rectangle() While REnum.MoveNext

R = CType(REnum.Current, Rectangle)

Console.WriteLine(R.Width * R.Height) End While

End Sub

The third Rectangle variable is added to the collection directly, without using an intermediate variable, as I did with the first two objects. The Rectangle object is initialized in the same line that adds the object to the collection. Then the REnum variable is set up and used to iterate through the items of the collection. At each iteration, the code saves the current Rectangle to the R variable, and it uses this variable to access the properties of the Rectangle object (its width and height).

Of course, you can iterate a collection without the enumerator, but with a For Each…Next loop. To iterate through a HashTable, you can use either the Keys or the Values collections. The code of Listing 11.13 populates a HashTable with Rectangle objects. Then it scans the items and prints their keys, which are strings, and the area of each rectangle.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE IENUMERATOR AND ICOMPARER INTERFACES 511

Listing 11.13: Iterating a HashTable with Its Keys

Protected Sub Button3_Click(ByVal sender As Object, _ ByVal e As System.EventArgs)

Dim hTable As New HashTable()

Dim r1 As New Rectangle(1, 1, 10, 10) hTable.Add(“R1”, r1)

r1 = New Rectangle(2, 2, 20, 20) hTable.Add(“R2”, r1)

hTable.add(“R3”, New Rectangle(3, 3, 2, 2)) Dim key As Object

Dim R As Rectangle

For Each key In hTable.keys

R = CType(hTable(key), Rectangle)

Console.WriteLine(“The area of Rectangle {0} is {1}”, Key.ToString, _ R.Width * R.Height)

Next End Sub

The code adds three Rectangle objects to the HashTable and then iterates through the collection using the Keys properties. Each item’s key is a string (“R1”, “R2”, and “R3”). The Keys property is itself a collection and can be scanned with a For Each…Next loop. At each iteration, we access a different item through its key, with the expression hTable(key). The output produced by this code is shown here:

The area of Rectangle R1 is 100

The area of Rectangle R2 is 400

The area of Rectangle R3 is 4

(I have used a format string with the WriteLine method to avoid a very long statement by embedding the values into the string.)

Alternatively, you can iterate a HashTable with an enumerator, but be aware that the GetEnumerator method of the HashTable collection returns an object of the IDictionaryEnumerator type, not an IEnumerator object. The IDictionaryEnumerator is quite similar to the IEnumerator, but it exposes additional properties. They are the Key and Value properties, and they return the current item’s key and value. The IDictionaryEnumerator object also exposes the Entry property, which returns both the key and the value. You can access the current item’s key and value either as

DEnum.Key and DEnum.Value, or as DEnum.Entry.Key and DEnum.Entry.Value. DEnum is a properly

declared enumerator for the HashTable:

Dim DEnum As IDictionaryEnumerator

Assuming that you have populated the hTable collection with the same three Rectangle objects, you can use the statements in Listing 11.14 to iterate through the collection’s items.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

512 Chapter 11 STORING DATA IN COLLECTIONS

Listing 11.14: Iterating a HashTable with an Enumerator

Dim hEnum As IDictionaryEnumerator hEnum = hTable.GetEnumerator While hEnum.MoveNext

Console.WriteLine(“The value of “ & hEnum.Key & “{0} is “ & hEnum.Value) Console.WriteLine(CType(hEnum.Value, Rectangle).Width * _

CType(hEnum.Value, Rectangle).Height)

End While

If you execute these statements after populating the HashTable collection with three Rectangles, they will produce the following output:

The value of R1 is {X=1,Y=1,Width=10,Height=10} 100

The value of R2 is {X=2,Y=2,Width=20,Height=20} 400

The value of R3 is {X=3,Y=3,Width=2,Height=2} 4

The Value property of the enumerator returns an object, which must be cast to the appropriate type, before you can call its members—unless the Strict option has been set to Off, of course.

VB.NET at Work: The Enumerations Project

The project Enumerations (Figure 11.4) on the CD shows how to iterate through an ArrayList and a HashTable with and without an enumerator. The code should be quite familiar to you by now, so I will not list it list here. You can open the project and examine its code and routines.

Figure 11.4

How to scan ArrayLists and HashTables with and without an enumerator

You can also enumerate arrays with an IEnumerator object. You must declare the enumerator variable as IEnumerator and then call the MoveNext method to iterate the array from within a loop. Listing 11.15 iterates through the elements of a string array with an enumerator.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com