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

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

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

160

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

Figure 6-4. Clicking View Detail allows you to drill down in more detail into just what went wrong with the program.

Click the + sign beside the first item in the list to view the detail of the exception, as I have in Figure 6-4.

There are quite a few interesting insights into the exception here. What you’re looking at is the type of exception (System.FormatException) and some of its properties. Of most interest is the Message property, which tells us that, as we expected, “Input string was not in a correct format.” You’ll also notice a property called InnerException. When an exception occurs, it could be “caught” by other code and then transformed into a new type of exception. For example, a method deep in a class may fail to parse some text into a Double, causing a FormatException to occur. This may get caught by some other code and transformed into a custom CantDoItException that you wrote yourself. In that case FormatException would become the inner exception. It’s a little like Russian dolls, with more-generic exceptions containing morespecific ones, each exposed through the InnerException property. You’ll see more of this in a little while.

For now, click the OK button to get rid of the dialog box, and then stop the program either by pressing Ctrl+Alt+Break or by clicking the VCR-like Stop Debugging button on the Visual Basic 2005 Express toolbar (see Figure 6-5).

Figure 6-5. You can click the square icon on the Debug toolbar to stop the program.

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

161

ON THE OTHER HAND…

It’s worth pointing out that you could have used the TryParse() method instead of Parse(). The big difference between the two is that TryParse() returns a True or a False value to indicate whether it could do the conversion, rather than raising an exception when things go wrong. Also, TryParse() takes a lot of parameters specifying exactly the format that should be used on the string, the string itself, and the variable that you want the parsed value dumped into. Using this method can get a little complicated and certainly detracts from what we’re trying to achieve here. Feel free to take a look at TryParse() in the online help.

Handling Exceptions

Visual Basic 2005 includes three keywords to give you some control over exceptions at runtime; Try, Catch, and Finally. Anything inside a Try block is code that you want to run. Code in the Catch block runs only when something goes wrong (and an exception occurs), and code in the Finally block is code that must always run no matter what.

Let’s see all this in action by fixing the broken division program you started earlier.

Try It Out: Catching Exceptions

The example you worked on earlier in this chapter has a pretty significant problem. If the user enters text in the text boxes, the program will crash. You’ll fix that now. Bring up the project again and go back into the code behind the button’s Click event. Add a Try...Catch block:

Public Class Form1

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

ByVal e As System.EventArgs) Handles divideButton.Click

Try

Dim dividend As Double = Double.Parse(dividendBox.Text)

Dim divisor As Double = Double.Parse(divisorBox.Text)

resultBox.Text = (dividend / divisor).ToString()

Catch ex As Exception

MessageBox.Show("Something went horribly wrong!")

End Try

End Sub

End Class

162

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

If you run the program after this change and enter text in one of the text boxes, you’ll find the program no longer crashes and instead shows a message box when something goes wrong.

Of course, this code doesn’t actually do anything with the exception. When the error occurs and is caught, there’s absolutely no indication of what the error was or how it occurred. Changing the code to find out this stuff is easy. Replace the existing Catch block with this one:

Catch ex As Exception

MessageBox.Show("Something went horribly wrong: " + _ ex.Message)

End Try

Make that change, and when the error occurs you’ll see a much more informative message about just what went wrong (see Figure 6-6).

Figure 6-6. By adding a parameter to the Catch block, you can reference the exception in code.

This still isn’t perfect, though. In fact, this is terrible, and you should never write a Catch block like this. When I first learned Java (which uses a similar Try...Catch syntax), it took me a while to realize just why this is so terrible.

Every exception inherits from System.Exception. When an exception occurs at runtime, Visual Basic 2005 looks for a Catch block that most closely matches the type of exception that occurred. If all you have is a Catch block that catches generic exception types, then you are effectively telling VB that all exceptions, regardless of type, are handled in that one block. This is bad because although you may think that only one

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

163

thing could ever trigger the Catch block, you’ll inevitably find something unexpected happening, which will get caught by the Catch block you’ve written. At that point something unexpected has happened to trigger the exception, and your code is assuming the wrong reason the exception was thrown. A much better way to write the code is like this:

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

ByVal e As System.EventArgs) Handles divideButton.Click

Try

Dim dividend As Double = Double.Parse(dividendBox.Text)

Dim divisor As Double = Double.Parse(divisorBox.Text)

resultBox.Text = (dividend / divisor).ToString()

Catch formatEx As FormatException MessageBox.Show("Please enter numbers, not text")

Catch ex As Exception

MessageBox.Show("Something unexpected occurred:" _ + ex.Message)

End Try

End Sub

This is much better. Now the code will respond to FormatException exceptions by displaying a message telling the user exactly what went wrong. If any other kind of exception occurs, though, you’ll get a detailed message about the unexpected exception.

There’s still a problem with the code, but it’s a subtle one. If you perform a valid calculation and then try one that fails, the result text box will still show the result of the previous successful one. You can use a Finally block to fix that. (You could also fix this problem by clearing out the text box before you even try to calculate anything, but then I wouldn’t be able to show you how the Finally block works at all—so forgive the slightly inelegant code solution here.)

When an exception occurs, the code stops. It stops at the point where the exception occurs and then it jumps to the nearest Catch block (more on how that’s calculated in a minute). So, any code beyond the line that fires the exception doesn’t run. A Finally block solves this by telling VB that no matter what happens, whether the code succeeds or fails, the code in the Finally block must be run.

164

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

Let’s make some changes to the code to use a Finally block:

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

ByVal e As System.EventArgs) Handles divideButton.Click

Dim result As String = String.Empty

Try

Dim dividend As Double = Double.Parse(dividendBox.Text)

Dim divisor As Double = Double.Parse(divisorBox.Text)

result = (dividend / divisor).ToString()

Catch formatEx As FormatException MessageBox.Show("Please enter numbers, not text")

Catch ex As Exception

MessageBox.Show("Something unexpected occurred:" _ + ex.Message)

Finally

resultBox.Text = result

End Try

End Sub

Although this example is trivial, our use of exception handling rocks! This is a much better way to do things. Now the code creates a string variable. The result of the division is stored in that variable, and the variable is dumped into the text box in the Finally block. So, if the division fails now, the text box with the result will be cleared. If it works, the text box shows the result.

Visual Basic’s exception handling support also provides a feature no other .NET language has—conditional catches. With these you can specify Catch blocks that run only when a certain exception type occurs and some other condition is true. You write them like this:

Try

Catch ex As Exception When a = 10

End Try

In this case, the code in the Catch block will run only if an exception occurs when variable a is set to 10.

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

165

Bubbling Exceptions

So far the code you’ve seen has the Catch block right there with the code that you’re expecting to throw an exception. However, exceptions can bubble up.

Let’s say you’re writing a method named FireEmployee(), that calls method

ProduceTerminationDocuments(), that calls method PrintEmployeeLetter(), and

that in turn throws an exception. Visual Basic 2005 will search for the first Catch block in

PrintEmployeeLetter(), ProduceTerminationDocuments(), and FireEmployee() that matches. If you had a Catch in ProduceTerminationDocuments(), for example, that matches the exception better than a handler in PrintEmployeeLetter(), then

ProduceTerminationDocuments()’ exception handler will fire. Here’s a brief code demonstration:

Module Module1

Sub Main()

Try

DoSomething()

Catch ex As Exception

Console.WriteLine("There was an exception: " + ex.Message

End Try

End Sub

Public Sub DoSomething()

Dim aNumber As Double = Double.Parse("This will throw!")

End Sub

End Module

If you were to key this in, the Catch block in Main() would trigger, even though the exception is actually thrown in DoSomething().

The moral of the story: you don’t need to catch an exception where it happens. If it makes more sense to catch it in a calling method, then do so. However, if you’re writing code that someone else is going to call, don’t forget to document that your code could throw an exception, and just what type of exception it could throw.

Throwing Exceptions

There’s a school of thought in Java, C#, and Visual Basic 2005 (again, I mention Java just because it has exactly the same exception-handling syntax as C#, which in turn has the same syntax as Visual Basic 2005) that says if your code encounters something wrong,

166

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

throw an exception. There’s another school of thought that says return a value that indicates an error. The guys in this second group tend to be old-school C and C++ guys because those languages didn’t have a rich exception-handling mechanism.

For example, let’s say you had a method that logged a user in. Without exceptions you could write it like this:

Public Function LoginUser(ByVal name As String, _

ByVal password As String) As Boolean

Dim loginSuccessful As Boolean

' Code here to actually try to log in

' and set loginSuccessful to true or false

Return loginSuccessful

End Function

What you’re indicating here is that there is a return value that will be set to True if the user successfully logs in, and False if the user doesn’t. Perhaps you don’t want the person calling your function to let their application go any further if the user fails to log in. The problem with this approach, though, is twofold.

First, you’re relying on something you can’t enforce. You return False if the user can’t log in and you hope that the person calling the code checks the value you return and does something with it. You have no guarantee that the person will, though.

Second, remember in the discussion of subroutines in Chapter 2 how I spoke about programming by intent? Well, this method name doesn’t do that. The method name LogInUser() implies that this method will log in a user no matter what. It doesn’t indicate that something needs to be checked or could go wrong.

So, you have two choices: either you can change the name of your method to something more obvious or you can throw an exception. Personally, I prefer the latter. This method will log a user in, no matter what. If something horrible happens, an exception can get thrown and the person calling this code (could be you, of course) will have no choice but to either deal with it or have their application crash.

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

167

So, how do you do that? Visual Basic 2005 has a special keyword built in for throwing exceptions: the aptly named Throw keyword. All you need to do is pass this a valid Exception object and the exception is thrown. So, you could change that preceding fragment of code to this:

Public Sub LoginUser(ByVal name As String, _

ByVal password As String)

Dim loginSuccessful As Boolean

' Code here to actually try to log in

' and set loginSuccessful to true or false

If Not loginSuccessful Then

Throw New Exception("The user failed to log in")

End If

End Sub

Now, at the end of the method, if the user didn’t manage to log in correctly, you create a new Exception object and throw it.

The constructor to the Exception object can take a single string parameter, as you can see. This sets up the Message property of the exception so that the code that catches this exception can tell the user something useful about what went wrong.

But this code is just as bad as a Catch block that catches generic exceptions. It’s not really very specific, is it? It’s a much better idea to create a specific type of exception, either by using one of the Exception classes from the .NET Framework, or by creating your own. Here’s how you’d create your own:

Public Sub LoginUser(ByVal name As String, _

ByVal password As String)

Dim loginSuccessful As Boolean

' Code here to actually try to log in

' and set loginSuccessful to true or false

168

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

If Not loginSuccessful Then

Throw New LoginException("The user failed to log in")

End If

End Sub

...

...

...

Public Class LoginException

Inherits Exception

End Class

Although this will work, the code is pretty bad from a design standards point of view. When you define your own exception types, there really are a number of important rules you must follow in the new class. Let’s take a look.

Custom Exceptions

Exceptions have lots of overloaded constructors, so when you create your own exception classes you really need to implement each of these three constructors. Also, all exceptions should be serializable. It’s a confusing term this early on in your Visual Basic career, but it’s a simple enough concept. When a class is serializable, Visual Basic 2005 knows how to turn that class into something that can be stored on disk, or transmitted over the network, at runtime. Thankfully, you don’t need to do anything special to make a class of your own be serializable, other than let .NET know that’s what you need.

Visual Basic provides a great snippet to walk you through all these requirements when you create your own exception types. In the code editor, simply right-click, choose Insert Snippet Common Code Patterns Exception Handling Define an Exception Class. The Visual Basic snippet system will then automatically dump a bunch of useful code into the editor, like this:

<Serializable()> _

Public Class ProblemException Inherits ApplicationException

C H A P T E R 6 H A N D L I N G E X C E P T I O N S

169

Public Sub New(ByVal message As String)

MyBase.New(message)

End Sub

Public Sub New(ByVal message As String, ByVal inner As Exception)

MyBase.New(message, inner)

End Sub

Public Sub New( _

ByVal info As System.Runtime.Serialization.SerializationInfo, _

ByVal context As System.Runtime.Serialization.StreamingContext)

MyBase.New(info, context)

End Sub

End Class

The first line of code here is an attribute. Attributes are beyond the scope of this book, so think of them simply as markers that the .NET runtime can use to find out information about a class. In this case, the attribute tells the .NET runtime that this class is serializable, just as every exception class should be.

Notice also that this class does not inherit from Exception. Instead it inherits from ApplicationException. According to the official Microsoft Framework Design Guidelines, this is wrong. The snippet that ships with Visual Basic 2005 Express has a bug! You should always derive your custom exceptions from the base Exception type. Deriving them from ApplicationException deepens the inheritance chain, making things a little more convoluted than perhaps they need to be. Also, by extending ApplicationException in all cases, you actually forgo the benefits that can be had from either reusing or extending the more specific exception types in the framework. In short then, when using this snippet, change the base class from ApplicationException to Exception.

The three constructors provide you with a means to add custom code when this exception is created. For example, you may want to automatically store information about this exception in a log file each time it gets created. Although you may never need to add any custom code, it is generally accepted to be good programming form to at least have these placeholders generated, as the snippet does for you. Then if you need to later add custom code, it’s a fairly painless exercise.

Finally, as with all snippets, the most important thing to remember about this code is that after the snippet adds it, change the highlighted areas (in this case it will be the class name) to the specific class name you want to use.