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

THE IENUMERATOR AND ICOMPARER INTERFACES 513

Listing 11.15: Enumerating an Array

Dim Names(4) As String

Names(0) = “Name 0” : Names(1) = “Name 1” Names(2) = “Name 2” : Names(3) = “Name 3” Dim arrayEnum As IEnumerator

arrayEnum = Names.GetEnumerator While arrayEnum.MoveNext

Console.WriteLine(arrayEnum.Current) End While

Custom Sorting

The Sort method allows you to sort collections, as long as the items are of the same base data type. If the items are objects, however, the collection doesn’t know how to sort them. If you want to sort objects, you must help the collection a little by telling it how to compare two objects. A sorting operation is nothing more than a series of comparisons. Sorting algorithms compare items and swap them if necessary. They don’t even swap the items; they simply rearrange a list of pointers to the items. The first pointer points to the first item in the sorted collection, the second pointer points to the second item in the sorted collection, and so on. The items themselves remain in their original positions.

All the information needed by a sorting algorithm to operate on any type of item is a function that compares two objects. Let’s say you have a list of persons, and each person is a Structure that contains names, addresses, e-addresses, and so on. The System.Collections class can’t make any assumptions as to how you want your list sorted. This collection can be sorted by any field in the structure (names, e-addresses, postal codes, and so on). Even if the collection contains a built-in object, like a Rectangle or Color object, the collection doesn’t know how to sort them.

The comparer is implemented as a separate class, outside all other classes, and is specific to a custom data type. Let’s say you have created a custom structure for storing contact information. The Person object is declared as a structure with the following fields:

Structure Person

Dim Name As String

Dim BDate As Date

Dim EMail As String

End Structure

To add an instance of the Person object to an ArrayList or HashTable, create a variable of Person type and initialize its fields as follows:

Dim p As New Person Dim aList As ArrayList

p.Name = “Adams, George” p.Bdate = #4/17/1957#

p.EMail = “gadams@example.com” aList.Add(p)

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

514 Chapter 11 STORING DATA IN COLLECTIONS

To add another element, you can either create a new Person object (variable p1, for example) or set the p variable to Nothing and then initialize it again.

p = Nothing

p.Name = “New Name” p.BDate = #1/1/1950#

‘ The EMail field is empty aList.Add(p)

This collection can’t be sorted with the simple form of the Sort method, because the compiler doesn’t know how to compare two Person objects. You must provide your own function for comparing two variables of the Person type. Once this function has been written, the compiler will be able to compare items and therefore sort the collection. This custom function, however, can’t be passed to the Sort and BinarySearch methods by name. You must create a new class that implements the IComparer interface and pass an IComparer object to the two methods. Here’s the outline of a class that implements the IComparer interface (then we’ll look at the implementation details of the function that actually compares two objects).

Class customComparer : Implements IComparer

Public Function Compare(ByVal o1 As Object, ByVal o2 As Object) _ As Integer Implements IComparer.Compare

{ function’s code } End Function

End Class

The name of the class can be anything. It should be a name that indicates the type of comparison it performs, or the type of objects it compares. The name of the custom function must be Compare, and it must implement the IComparer.Compare interface. What exactly do we mean by “implement an interface”? As you have seen, all classes expose some standard members. The method ToString, for example, is a standard method, and the Framework knows how to implement it. It simply returns the name of the class. There are situations, however, where we know that we’re going to need a member, we know the name of the member, but we just can’t implement it.

The collection classes, for example, expose a Sort method. In order to sort their items, they must be able to compare two elements and figure out which one is first. The comparison can be carried out for the base types, but not for objects. So, they provide a placeholder, where you must place a function that knows how to compare two instances of the object. This ability to write classes that provide placeholders for the actual implementation of a method is known as interface. If you write a function that compares two objects of the specific type and you pass it to the Sort method, this function will be called in the place of the Compare method. The Sort method calls the Compare method of the class that represents the objects you’re comparing. If it finds one, it uses it. If not, the Sort method won’t work. The IComparer interface is the class’s way of incorporating your custom comparer function into its Sort method.

Let’s get back to our example. To use the custom function, you must create an object of customComparer type (or whatever you have named the class) and then pass it to the Sort and BinarySearch methods as argument:

Dim CompareThem As New customComparer aList.Sort(CompareThem)

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE IENUMERATOR AND ICOMPARER INTERFACES 515

You can combine the two statements in one by initializing the customComparer variable in the line that calls the Sort method:

aList.Sort(New customComparer())

You can also use the equivalent syntax of the BinarySearch method to locate a custom object that implements its own IComparer interface:

BinarySearch(object, New customComparer())

This is how you use a custom function to compare two objects. Everything is the same, except for the name of the class, which is different every time. The last step is to write the code that compares the two objects and returns an integer value, indicating the order of the elements. This value should be –1 if the first object is smaller than the second object, 0 if the two objects are equal, and 1 if the first object is larger than the second object. “Smaller” here means that the element appears before the larger one when sorted in ascending order. Listing 11.16 is the function that sorts Person objects according to the Age field.

Listing 11.16: A Custom Comparer

Class PersonAgeComparer : Implements IComparer

Public Function Compare(ByVal o1 As Object, ByVal o2 As Object) As Integer _ Implements IComparer.Compare

Dim person1, person2 As Person Try

person1 = CType(o1, Person) person2 = CType(o2, Person)

Catch compareException As system.Exception Throw (compareException)

Exit Function End Try

If person1.BDate < person2.BDate Then Return -1

Else

If person1.BDate > person2.BDate Then Return 1

Else Return 0

End If End If

End Function End Class

The code could have been considerably simpler, but I’ll explain momentarily why the Try statement is necessary. The comparison takes place in the If statement. If the first person’s birth date is numerically smaller than the second person’s, the function returns the value –1. If the first person’s birth date is numerically smaller than the second person’s, the function returns 1. Finally, if the two values are equal, the function returns 0.

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

516 Chapter 11 STORING DATA IN COLLECTIONS

The code is straightforward, so why the error-trapping code? Before we perform any of the necessary operations, we convert the two objects into Person objects. It’s not unthinkable that the collection with the objects you want to sort contains objects of different types. If that’s the case, the CType() function won’t be able to convert the corresponding argument to the Person type, and the comparison will fail. The same exception that would be thrown in the function’s code is raised again from within the error handler, and it’s passed back to the calling code.

The Person objects can be sorted in many different ways. You may wish to sort them by ID, name, and so on. To accommodate multiple sorts, you must implement several classes, each one with a different Compare function. Listing 11.17 shows two classes that implement two different Compare functions for the Person class. The PersonNameComparer class compares the names, while the PersonAgeComparer class compares the ages.

Listing 11.17: A Class with Two Custom Comparers

Class PersonNameComparer : Implements IComparer

Public Function Compare(ByVal o1 As Object, ByVal o2 As Object) As Integer _ Implements IComparer.Compare

Dim person1, person2 As Person Try

person1 = CType(o1, Person) person2 = CType(o2, Person)

Catch compareException As system.Exception Throw (compareException)

Exit Function End Try

If person1.Name < person2.Name Then Return -1

Else

If person1.Name > person2.Name Then Return 1

Else Return 0

End If End If

End Function End Class

Class PersonAgeComparer : Implements IComparer

Public Function Compare(ByVal o1 As Object, ByVal o2 As Object) As Integer _ Implements IComparer.Compare

Dim person1, person2 As Person Try

person1 = CType(o1, Person) person2 = CType(o2, Person)

Catch compareException As system.Exception Throw (compareException)

Exit Function End Try

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

THE IENUMERATOR AND ICOMPARER INTERFACES 517

If person1.BDate > person2.BDate Then

Return -1

Else

If person1.BDate < person2.BDate Then

Return 1

Else

Return 0

End If

End If

End Function

End Class

To test the custom comparers, create a new application and enter the code of Listing 11.17 (the two classes) in a separate Class module. Don’t forget to include the declaration of the Person Structure. Then place a button on the form and enter the code of Listing 11.18 in its Click event handler. This code adds three persons with different names and birth dates to an ArrayList.

Listing 11.18:Testing the Custom Comparers

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click

Dim AList As New ArrayList()

Dim p As Person

Populate collection p.Name = “C Person”

p.EMail = “PersonC@sybex.com” p.BDate = #1/1/1961#

If Not AList.Contains(p) Then AList.Add(p) p.Name = “A Person”

p.EMail = “PersonA@sybex.com” p.BDate = #3/3/1961#

If Not AList.Contains(p) Then AList.Add(p) p.Name = “B Person”

p.EMail = “PersonB@sybex.com” p.BDate = #2/2/1961#

If Not AList.Contains(p) Then AList.Add(p)

Print collection as is

Dim PEnum As IEnumerator PEnum = AList.GetEnumerator

ListBox1.Items.Add(“Original Collection”) While PEnum.MoveNext

ListBox1.Items.Add(CType(PEnum.Current, Person).Name & vbTab & _

CType(PEnum.Current, Person).BDate)

End While

Sort by name, then print collection

ListBox1.Items.Add(“ “)

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com

518 Chapter 11 STORING DATA IN COLLECTIONS

ListBox1.Items.Add(“Collection Sorted by Name”)

AList.Sort(New PersonNameComparer())

PEnum = AList.GetEnumerator

While PEnum.MoveNext

ListBox1.Items.Add(CType(PEnum.Current, Person).Name & vbTab & _

CType(PEnum.Current, Person).BDate)

End While

Sort by age, then print collection

ListBox1.Items.Add(“ “) ListBox1.Items.Add(“Collection Sorted by Age”) AList.Sort(New PersonAgeComparer())

PEnum = AList.GetEnumerator While PEnum.MoveNext

ListBox1.Items.Add(CType(PEnum.Current, Person).Name & vbTab & _

CType(PEnum.Current, Person).BDate)

End While

End Sub

The four sections of the code are delimited by comments, which appear in bold in the listing. The first section populates the collection with three variables of the Person type. The second section prints the items in the order in which they were added to the collection:

C Person 1/1/1961 A Person 3/3/1961 B Person 2/2/1961

The third section of the code calls the Sort method passing the PersonNameComparer custom comparer as argument, and it again prints the contents of the ArrayList. The names are listed now in alphabetical order:

A Person 3/3/1961 B Person 2/2/1961 C Person 1/1/1961

In the last section, it calls the Sort method again, this time to sort the items by age, and prints them:

C Person 1/1/1961 B Person 2/2/1961 A Person 3/3/1961

Copyright ©2002 SYBEX, Inc., Alameda, CA

www.sybex.com