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

Beginning Visual Basic 2005 Express Edition - From Novice To Professional (2006)

.pdf
Скачиваний:
387
Добавлен:
17.08.2013
Размер:
21.25 Mб
Скачать

340

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

Notice that the first thing you do here is declare a new private variable. This variable is used by the actual predicate; you need some way to set up the data that you want to search for and that the predicate can access to check whether the required item has been found in the list.

The FindAnEmployee() method creates an instance of the Employee class and stores it in your private variable. The code then sets up the Name property of this new Employee instance to the value passed into your function. So, in your code you want to search for an employee called Pete Wright, so you set the name of the employee to Pete Wright.

All that remains is to call the actual Find() methods of the list:

If staff.Exists(AddressOf FindEmployeeByName) Then Dim matchedEmployee As Employee = _

staff.Find(AddressOf FindEmployeeByName) Console.WriteLine(" {0} works in {1}", _

matchedEmployee.EmployeeName, matchedEmployee.Department)

End If

The first one is Exists(). This just searches through the list, using the address of your predicate function, and will return True if the predicate function eventually finds a match, and False if it never does. Assuming it does, though, you drop into a code block to actually grab the matching employee and print some details.

You grab the matching employee with a call to staff.Find(). This will return the first matching object that is found. This can be a problem if there is more than one match in the list, obviously, but you’ll look at a solution for that in a moment. For now take a look at the predicate function itself:

Function FindEmployeeByName(ByVal employeeToCheck As Employee) As Boolean Return (employeeToCheck.EmployeeName.Equals( _

_employeeToFind.EmployeeName)) End Function

All this method does is compare the Name properties of two Employee objects: the one passed in from the list’s Find() method, and the private variable you created earlier. It returns True if the employee names match, and False if they don’t.

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

341

Before you go any further, you’ll add some candy to the code to smarten up the output a little, and stub the second method that you haven’t written yet. Go ahead and add the highlighted lines of code to your

Module1.vb code:

Sub FindAnEmployee(ByVal staff As List(Of Employee), ByVal name As String)

Console.WriteLine("Trying to find employee {0}", name)

_employeeToFind = New Employee() _employeeToFind.EmployeeName = name

If staff.Exists(AddressOf FindEmployeeByName) Then

Console.WriteLine(" Found the employee")

Dim matchedEmployee As Employee = staff.Find(AddressOf _ FindEmployeeByName)

Console.WriteLine(" {0} works in {1}", _ matchedEmployee.EmployeeName, matchedEmployee.Department)

Else

Console.WriteLine(" No, I'm afraid I couldn't find the employee")

End If

End Sub

Function FindEmployeeByName(ByVal employeeToCheck As Employee) As Boolean Return (employeeToCheck.EmployeeName.Equals( _

_employeeToFind.EmployeeName)) End Function

Sub FindStaffInDepartment(ByVal staff As List(Of Employee), _ ByVal department As String)

'

End Sub

342

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

Run the program now and you’ll see the output in Figure 13-2.

Figure 13-2. At last, you can find items in the List.

The Exists() and Find() methods both look for the first match and return a result.

On the other hand, the FindAll() method returns another generic List containing all matches.

Code up the FindStaffInDepartment() method and the predicate you’re going to use to search for matching departments:

Sub FindStaffInDepartment(ByVal staff As List(Of Employee), _

ByVal department As String)

Console.WriteLine("Trying to find all staff that work in {0}", _ department)

_employeeToFind = New Employee() _employeeToFind.Department = department

Dim matchedEmployees As List(Of Employee) = _ staff.FindAll(AddressOf FindEmployeesInDepartment)

For Each foundEmployee As Employee In matchedEmployees Console.WriteLine(" {0} works in {1}", _

foundEmployee.EmployeeName, foundEmployee.Department)

Next

End Sub

Function FindEmployeesInDepartment(ByVal employeeToCheck As Employee) As Boolean

Return (_employeeToFind.Department.Equals( _ employeeToCheck.Department))

End Function

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

343

The code is really very similar to the previous example. You set up the private object with the name of the department you want to search for (it’s passed into your method), and then call a Find() method (in this case FindAll()) passing in the address of the predicate you want to use to do the actual matching.

The result of FindAll(), as I mentioned earlier, is a generic List of the same type as the one you are searching. All you need to do with the result, then, is iterate over the list with a For Each command and then print out the matching object details. After you’ve keyed in the new code, run the app and take a look at the results. It should look like Figure 13-3.

Figure 13-3. Searching for many matches is just as easy as searching for one.

The other functions all follow exactly the same pattern: create a matcher method, instantiate a predicate with it, and pass the predicate into the Find() method.

Sorting Lists

Sorting lists works pretty much the same as finding items in a list. Again, you create a function and then pass its address to the list’s Sort() method. The function in this case, though, is called a comparison instead of a predicate.

A typical comparison function signature looks like this:

Function Compare(ByVal firstEmployee As Employee, _

ByVal secondEmployee As Employee) As Integer

It takes two parameters of the same types and returns an integer. If the integer is less than 0, the first object is less than the second. If the integer is 0, the two objects are the same. It figures then, that if the integer is positive, the first object is greater than the second. It’s up to the code in the comparison function to figure out how to compare two objects such as Employees, which don’t have a typical numeric type value.

Let’s extend the previous sample with a comparer to sort the list.

344

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

Try It Out: Sorting the List

Load up the code you worked on earlier, and add stubs for a sorting routine, and a comparison function to the

Module1.vb file:

Sub Main()

Dim staff As New List(Of Employee) staff.Add(New Employee("Peter Wright", "IT"))

staff.Add(New Employee("Heather Wright", "Usability")) staff.Add(New Employee("Dominic Shakeshaft", "Editorial")) staff.Add(New Employee("Grace Wong", "Management")) staff.Add(New Employee("Gary Cornell", "Management"))

FindAnEmployee(staff, "Peter Wright")

FindStaffInDepartment(staff, "Management")

SortTheList(staff)

Console.ReadLine()

End Sub

Sub SortTheList(ByVal staff As List(Of Employee))

End Sub

Function CompareEmployees(ByVal firstEmployee As Employee, _

ByVal secondEmployee As Employee) As Integer

End Function

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

345

Let’s work on the comparison function CompareEmployees() first. You’ll strip out the surnames of the two employees passed into the function and compare them with the string’s built-in CompareTo() function:

Function CompareEmployees(ByVal firstEmployee As Employee, _

ByVal secondEmployee As Employee) As Integer

Dim firstSurname As String = _ firstEmployee.EmployeeName.Substring( _

firstEmployee.EmployeeName.IndexOf(" ") + 1)

Dim secondSurname As String = _ secondEmployee.EmployeeName.Substring( _

secondEmployee.EmployeeName.IndexOf(" ") + 1)

Return firstSurname.CompareTo(secondSurname)

End Function

I know it looks nasty, but it really isn’t. The code just finds the index of the first space in the employee name (because in this example a space separates the first name and surname). Armed with this, you can call substring to strip out the second half of the employee name into two variables: firstSurname and secondSurname. With those two strings, you can return the result of string.CompareTo() to do your work for you.

With the function complete, all you need to do now is complete your SortTheList() method:

Sub SortTheList(ByVal staff As List(Of Employee))

Console.WriteLine("The full list of sorted employees follows...") staff.Sort(AddressOf CompareEmployees)

For Each emp As Employee In staff

Console.WriteLine(" {0}", emp.EmployeeName)

Next

End Sub

346

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

Easy, isn’t it. The address of CompareEmployees is simply passed to Sort(). Just as with the Find() routines, the Sort() method just iterates through the list calling the comparison function. When Sort() is done, we iterate through the list and print out all the items in it.

Run the code and you’ll notice the output of the program has changed to now show the sorted list, as in Figure 13-4.

Figure 13-4. Sorting the list is as easy as searching for items.

Dictionaries

A straight list is great if you just want to store a bunch of information in an array-like structure, and perhaps sort it, as you saw. If you need to search for something, you’ve got a great deal of flexibility courtesy of the Find() methods. The search methods, though, are a little inefficient, because behind the scenes the search code in the List iterates through every single item in the list and then calls out to a predicate function that you define. Sometimes you need speed over flexibility, and that’s where the generic Dictionary collection comes into play.

The Dictionary lets you store a mass of information as a combination of keys and values, just like a real dictionary, in fact. If you grab your nearest handy copy of The Oxford English Dictionary, you’ll find that to find the meaning of a word, you look up the word.

The word then, is the key. When you find the key, you find the value of the key, which is its textual definition.

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

347

Something quite subtle happens when you try to find something in a real dictionary as well. You know that the dictionary is ordered alphabetically. Although the dictionary holds hundreds of thousands of words, it’s quite easy to zero in on a specific word definition very quickly by hand. .NET’s version of a dictionary uses something called hashes. If you studied computer science in college, you probably know all about hashes. Well done. If (like me) you didn’t study computer science in college, you’ll have little idea of what a hash is, and the good news is that you don’t need to. A hash is really just a unique number that gets produced after you throw a bunch of stuff (such as characters in a string) into a hashing algorithm. Armed with the hash, .NET is able to easily and quickly find a key in a dictionary, and from the key find the value. Thankfully, the base object type in .NET knows how to hash itself, and so do all the simple types (strings, and so on), so this isn’t something you ever really need to worry about.

Working with a Dictionary

Because a Dictionary needs to store two things for every item it contains (a unique key and the matching value), you need to declare two types when you instantiate a

Dictionary, like this:

Dim productList As New Dictionary( Of TKey, TValue)

In this snippet, we’re setting up a Dictionary to hold products. Each product has a unique ProductKey object associated with it.

If you were producing an electronic version of a real-world lexical dictionary, you might instantiate the Dictionary like this:

Dim realDictionary As New Dictionary(Of String, String)

Here both the key and value are strings, just as in a real dictionary. Adding items to the Dictionary works pretty much the same as adding items to a standard list, except that you need to pass in two items to the Add() method:

dictionary.Add( key, value )

Getting items out of the Dictionary looks just like you are working with a standard array, but instead of specifying an array index, you specify the key of the item that you want to find:

Dim wordDefinition As String = dictionary( wordToLookup )

348

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

Because it’s so easy to find individual items in the Dictionary based on their keys, dictionaries don’t have a set of Find() methods like a list does; you don’t need them:

dictionary.Add( "computer", "One that computes" ) Dim definition As String = dictionary("computer")

You can, however, check to see whether something already exists in a Dictionary, with the ContainsKey() and ContainsValue() methods. Both accept a single object as a parameter and return True or False to show whether a Dictionary contains the object passed in. You could use either of these, for example, to check whether something already exists in the Dictionary before you try to add it, and thus prevent duplicates.

Be careful though; although it’s easy to find out whether a specific value is already in the list, there’s no quick and simple way to grab a value from a Dictionary unless you happen to know the key. So, the ContainsValue() method really exists only to make absolutely certain that you aren’t adding duplicates to the list. For example:

Dim authors As New Dictionary(Of Integer, String)

If Not authors.ContainsValue("Pete Wright") Then authors.Add(1, "Pete Wright")

End If

There is a way around this limitation, still using generics, that I’ll show you at the end of the chapter when you take a look at creating your own generic types.

Anyway, let’s pull this all this together with a nice little app to show just how to use dictionaries properly.

Try It Out: Dictionaries

In this example you’re going to put together a little console app that will build a list of products, perhaps the beginnings of a product catalog application. So, first of all you need to add a new file to the project to hold the classes that define the product. Like all product catalogs, yours uses some obscure product identification code that you’ll actually represent in its own product number class.

C H A P T E R 1 3 L I S T S A N D G E N E R I C S

349

First, go ahead and add a new class file to the project—call it Products.vb. When the code editor opens, remove the class definition and replace it with two more, like this:

Public Class Product

End Class

Public Class ProductNumber

End Class

The Product class will hold the actual product details, including a name, description, and the product’s unique number (you’ll need that for the key in the Dictionary, because Dictionary keys have to be unique). In addition, the product number is defined in its own class because it’s a little complex.

Let’s start work on the ProductNumber class first. Go ahead and fill it out to look like the following code:

Public Class ProductNumber

Public ManufacturerCode As String

Public CategoryCode As String

Public Number As Integer

Public Sub New(ByVal code As String)

Dim codeParts() As String = code.Split(New Char() {"-"})

ManufacturerCode = codeParts(0)

CategoryCode = codeParts(1)

Number = Integer.Parse(codeParts(2))

End Sub

Public Overrides Function ToString() As String

Return String.Format("{0}-{1}-{2}", _

ManufacturerCode, CategoryCode, Number)

End Function

End Class