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

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

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

350

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

The class holds three public properties that define the product number: a manufacturer code, a category code, and an actual product number. It’s pretty much like some real-world product numbers I’ve seen. To simplify using it, the constructor takes in a string, where each part of the number is separated by a minus sign (for example, MSFT-SFTW-101). The constructor uses the string.split() method to get at each part of the product number (for example, MSFT, SFTW, and 101) in their respective properties. Finally, you override the base Object.ToString() method to get at a simple representation of the entire code as one entity again.

Next, you define the Product class itself. This one is real simple:

Public Class Product

Private _productID As ProductNumber

Public ReadOnly Property ProductID()

Get

Return _productID

End Get

End Property

Public ProductName As String

Public Description As String

Public Sub New(ByVal productCode As String, ByVal name As String, _ ByVal description As String)

_productID = New ProductNumber(productCode) ProductName = name

description = description

End Sub

End Class

The Product constructor takes three parameters to allow users to create a product instance and set up the member variables in one go. Notice it takes a string as the product code, which it hands off to a new instance of the ProductNumber class for decoding.

Okay, so now you have the background out of the way, you’re ready to look at the Dictionary side of things.

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

351

As I mentioned earlier, a Dictionary requires two things for every item it contains: a value and a unique key. In this Dictionary you’ll use a ProductNumber object for the key, and a Product object for the value. Hop back over to the Module1.vb class file and start typing into the Main() subroutine:

Imports System.Collections.Generic

Module Module1

Sub Main()

Dim catalog As New Dictionary(Of ProductNumber, Product)

SetupTheCatalog(catalog)

Console.ReadLine()

End Sub

Sub SetupTheCatalog(ByVal dict As Dictionary(Of ProductNumber, Product))

Dim testproduct As New Product( _

"WBS-SFTW-101", "CoolSoftware", "A neat piece of software")

dict.Add(testproduct.ProductID, testproduct)

testproduct = New Product( _

"WBS-SFTW-202", "NetSoftware", "A cool piece of Internet software") dict.Add(testproduct.ProductID, testproduct)

End Sub

End Module

First things first—you create the Dictionary itself. Because the Dictionary requires two things for each item (the key and the value), you specify two types in the angle brackets to set this up, instead of just one when working with a simple List. Just as I said it would, the Dictionary uses ProductNumber objects as keys, and Product objects as the values.

Next, the code calls your new SetupTheCatalog() function included in the preceding code. This just creates two Products and then calls dict.add() to add them to the Dictionary. Notice again that you need to pass two objects into the Dictionary for each item: the product’s unique ProductID (which is a ProductNumber object) and the new Product itself.

352

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

So far, so good. If you run the program, it will indeed create a new Dictionary and stick a couple of items in it, but you don’t really get to see what it’s doing or how. So the next step is to look at getting stuff out to check that the Dictionary is working properly. Let’s go back and add some more code into the Main() function:

Sub Main()

Dim catalog As New Dictionary(Of ProductNumber, Product)

SetupTheCatalog(catalog)

Try

Dim numToFind As New ProductNumber("WBS-SFTW-202")

If catalog.ContainsKey(numToFind) Then

Console.WriteLine("Apparently, the product does exist")

Else

Console.WriteLine("ContainsKey fails")

End If

Dim foundProduct As Product = catalog(numToFind) If Not foundProduct Is Nothing Then

Console.WriteLine("{0}: {1}" + vbCrLf + " {2}", _ foundProduct.ProductID, foundProduct.ProductName, _ foundProduct.Description)

End If

Catch ex As KeyNotFoundException Console.WriteLine( _

"Couldn't find the product you asked for, sorry")

End Try

Console.ReadLine()

End Sub

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

353

The first big difference between dictionary-based code and normal list-based code you’ll notice straight away: you start the new code in a Try...Catch block. The reason for this is that you don’t use Find() methods with a Dictionary. You don’t need to, because you can directly get at items inside a Dictionary by specifying the key just as if it were an index. Take a look at the first few lines of code inside the Try block to see this:

Try

Dim numToFind As New ProductNumber("WBS-SFTW-202")

If catalog.ContainsKey(numToFind) Then

Console.WriteLine("Apparently, the product does exist")

Else

Console.WriteLine("ContainsKey fails")

End If

Dim foundProduct As Product = catalog(numToFind)

The code creates a new ProductNumber object, which you use first to see whether the Dictionary contains that value as a key. This is done with a call to catalog.ContainsKey(). If this returns True, then yes, the Dictionary does contain the key you are searching for and so you go ahead and use this ProductNumber to index into the Dictionary and grab the value you want:

Dim foundProduct As Product = catalog(numToFind)

It looks just like it would if you were grabbing something out of an array. Instead of using a number to specify an item by its array index number, you instead hand over a Dictionary key. Now, this is why the code is in a Try...Catch block.

If you try to grab an item from the Dictionary with a key that doesn’t exist in the Dictionary, you get an exception. A KeyNotFoundException exception, in fact. So even if you are almost completely certain that everything is going to be fine and the key will be found, you really should wrap your code in a nice Try...Catch block just in case; the last thing you really want is for your program to blow up in front of a user just because you didn’t want to type in a few extra lines of code to be supersafe. The eagle eyed among you may have noticed that if I’d restructured this code a little, then you wouldn’t need a Try...Catch block at all. I could only try to grab a product if ContainsKey returns true. Obviously, if I’d done that though, you wouldn’t have been able to see what happens when things go wrong and how to cope with them.

So now you’re all done. You have your Product and ProductNumber classes in place, and some code to build up a Dictionary with them and then grab items. So run the code and see what happens (Figure 13-5).

354

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

Figure 13-5. The code fails if you don’t take some special steps when working with custom classes for keys.

It fails. Take a look at the code, though, and it’s not really obvious just why it fails. After all, when you do the searching, you specify the key correctly. Looking through the function you wrote that builds up the Dictionary items, you can see that the item you’re looking for really is there. So what’s going on?

You may have guessed that it’s the same problem you met earlier with the List. The Contains() method, and the Dictionary itself when you try to index it with a key, compares to see whether two object references (the one you say you want, and the key itself in the list) refer to exactly the same object in memory. In this case, they don’t. There is a solution, though (and this applies to lists as well).

Think way back to when we talked through objects and how they work. Every single class you create in code derives from System.Object. Now, System.Object has a few handy methods in it, one of which you’ve already met: ToString(). There are two others: Equals() and GetHashCode(). The Equals() method is used when comparing objects. Normally when you compare two objects, the .NET default comparer kicks in that, to be frank, is pretty much useless. You can, of course, override this and provide a much more meaningful implementation. This is particularly useful when working with generic collections like the Dictionary. Now, GetHashCode() is useful in a Dictionary because you need some way of assigning a big, unique number to every object instance that you create; it’s how .NET finds a specific object in a Dictionary in lightning-fast speed. Normally, this is a tricky thing to do, but in this case (ProductNumbers) it’s dead easy. Because you can represent the entire product number as a unique string, you can just call the string’s

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

355

GetHashCode() method. Go ahead and add these two methods to the ProductNumber class, and that should also fix your Dictionary search problem:

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

Public Overrides Function Equals(ByVal obj As Object) As Boolean If Not TypeOf obj Is ProductNumber Then

Throw New InvalidCastException( _

"Can only compare a ProductNumber to another ProductNumber")

End If

Return Me.ToString().Equals(obj.ToString()) End Function

Public Overrides Function GetHashCode() As Integer

Return Me.ToString().GetHashCode()

End Function

End Class

356

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

The Dictionary’s ContainsKey() method uses Equals() to see whether a key in the Dictionary matches the one you’re searching for. Your implementation of GetHashCode() just uses your ToString() override to call GetHashCode() on the string representation of the ProductNumber, and the Dictionary uses that to work out the hash code for each item.

Run the program now and you’ll find it works like a charm, as you can see in Figure 13-6.

Figure 13-6. After you’ve implemented the GetHashCode() and Equals() methods on your ProductNumber, everything slots into place with the Dictionary.

Stacks and Queues

We’ve spent a while now looking at lists and dictionaries and how they work. The rest of the generic collections (Stack, Queue) work pretty much the same way, so we’re not going to spend a great deal more time going over them in detail. However, it’s certainly worth pointing out what each of these collections do and how you can use them.

Stack

It’s a programming cliché, but I’m going to explain what a Stack is the same way it has been explained to generation after generation of newborn coder. Think of a Stack like a stack of plates—in fact, a stack of very, very hot plates. As you add plates to the stack, you drop them on top of the stack. However, as the stack grows, there’s only one way to get a single plate out, and that’s straight off the top. A stack, then, is sometimes known as a last- in-first-out data structure; the last item added is always the first item you can retrieve. The other thing with a stack, of course, is that at any time you can take a look at the item on top of the stack without actually pulling the item off.

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

357

Putting an item onto the stack is known as pushing it. Pulling an item off the stack is known as popping it. Taking a look at the top item is known as peeking at it. These are the same names of the methods that the Stack collection uses to work with items: Push(), Pop(), Peek(). So, you add items with Stack.Push(), remove them with Stack.Pop(), and take a look at the top item with Stack.Peek().

Queue

So if a Stack is a last-in-first-out data structure, a Queue is a first-in-first-out data structure. Think of it just as a real-life queue of people. The first person into the queue is the first person to get out of it (unless it’s a huge queue and people just decide to walk away—we’ll ignore that facet of the real-life structure).

Adding an item to the Queue is called enqueuing. Removing an item (the first item in the Queue, of course) is called dequeuing. As with a Stack, you can also take a Peek() at the first item in the queue. Just as with the Stack, these are the actual names of methods. Add

an item with Queue.Enqueue(), remove the first item with Queue.Dequeue(), and take a look at the first item with a call to Queue.Peek().

Creating Your Own Generics

Occasionally you’ll want to create your own generic type. You already know how to “use” a generic type, such as List(Of T), Stack(Of T), and so on. All you need to know now is how to create a generic type. Let’s imagine you wanted to produce a class that prints out the value it contains. Okay, sure, it’s a little contrived, but it gets the basic syntax out of the way:

Public Class PrintSomethingClass(Of T)

Dim _value As T

Public Sub New(ByVal valueToStore As T) _value = valueToStore

End Sub

Public Sub PrintTheValue()

Console.WriteLine("{0}", _value)

End Sub

End Class

358

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

Let’s walk through this. First, the class definition:

Public Class PrintSomethingClass(Of T)

What we’re doing here is creating a class called PrintSomethingClass and setting it up as a generic. The “type” the class will work with is T.

Next, take a look at the private member inside the class: Dim _value As T

This is just declaring a private member variable, and the type is the type used when you instantiate the class. For example, say you instantiated the class with this:

Dim myObject As PrintSomethingClass(Of Integer);

Then at compile time, wherever you see T in the class definition, it gets replaced with Integer. Think of a generic as a way of doing a search and replace, but automatically at compile time.

So, armed with that knowledge, the method definition should be easy to follow:

Public Sub New(ByVal valueToStore As T) _value = valueToStore

End Sub

Again, T would get replaced with the type used to instantiate the class, Integer in our previous example.

Summary

To the uninitiated, generics can be a somewhat intimidating feature of Visual Basic. After all, Java only just got generics, and now Visual Basic has caught up, so obviously it’s some big and scary new high-end language feature, right? Well, no. Generics add a great deal of flexibility to Visual Basic and allow you to create type-safe collections and classes easily.

The generic collections in fact also give you a lot of flexibility in how you manage your data within your programs in an organized fashion. As you move now toward writing your own programs, you’ll find more and more use for flexible data structures such as those in the System.Collections.Generic namespace.

C H A P T E R 1 4

■ ■ ■

Files and Streams

At work I write code that almost exclusively talks to huge databases. At home, though, most of the code I write deals exclusively with files and streams (I’ll tell you what streams are in a moment). I have a bunch of small programs called spiders that hit the Internet on my behalf at set times and download data into files for me; I may download video from a favorite video news site, or have another program build me a totally unique web page with snippets of news and information from around the world. I even have programs that handle automatically backing up data for me. All these things work with files and streams.

I’m sure you’ve already come across files. It’s hard to avoid them. When you save your family newsletter from Microsoft Word, you save it to a file. When you load up Quicken to balance your checking account, Quicken stores your checking account information along with all your transactions in sets of files. Microsoft Windows itself is nothing more than a collection of files that contain specific data and functionality to manage your computer and the programs you run.

Streams, on the other hand, you may not have heard of in a technical sense. A stream is nothing more than a flow of digital information. Think about your web browser for a moment. You key in a URL (such as www.apress.com, for example), and a web page magically appears in the browser. What actually happens behind the scenes is that the browser connects to a remote server and then opens a stream of data. That stream just happens to contain HTML information necessary to render a web page. Another way to think about it is as a stream of digital water. When you key in a URL to your web browser, you effectively tell it to stick its head in the water and take a huge gulp of all the digital water the stream contains.

So, the obvious next question is, why do we have streams and files in the same chapter? It all boils down to the great way that the .NET Framework is designed. In .NET a stream could be a source of information from a remote server, from a string variable in your own code, from a block of memory you’ve set aside, or more importantly from a file. If you want to read data from a file, you open its stream and grab everything you find in it. Similarly, if you want to create a new file, you open a stream and throw information into it. The great thing about .NET is that because streams apply to so many different things, you can easily write a program to handle one data source (a file, for example) and then switch it later to work with variables, or even servers on the Internet. It’s a great model.

359