Chapter 6
Exception Handling and Debugging
What's in this chapter?
The general principles behind exception handling
The Try…Catch…Finally structure for trapping exceptions
How to send exceptions to other code using the Throw statement
Obtaining information about an exception by using the exception object's methods and properties
Event logging and simple tracing, and how you can use these methods to obtain feedback about how your program is working
Production quality applications need to handle unexpected conditions. In .NET this is done with the structured exception syntax. When an unexpected condition arises .NET does not generate error codes. Instead when an unexpected condition occurs, the CLR creates a special object called an exception. This object contains properties and methods that describe the unexpected condition in detail and provide various items of useful information about what went wrong.
This chapter covers how structured exception handling works in Visual Basic. It discusses the common language runtime (CLR) exception handler in detail and illustrates some programming methods that are efficient when catching exceptions.
Property | Description |
HelpLink | A string indicating the link to help for this exception. |
InnerException | Returns the exception object reference to an inner (nested) exception. |
Message | A string that contains a description of the error, suitable for displaying to users. |
Source | The name of the object that generated the error. |
StackTrace | A read-only property. The stack trace is a list of the method calls at the point at which the exception was detected. That is, if MethodA called MethodB, and an exception occurred in MethodB, then the stack trace would contain both MethodA and MethodB. |
TargetSite | A read-only string property that holds the method that threw the exception. |
Method | Description |
GetBaseException | Returns the first exception in the chain |
ToString | Returns the error string, which might include as much information as the error message, the inner exceptions, and the stack trace, depending on the error |
There are many types of exception objects in the .NET Framework that derive from the base Exception class. Each is customized for a particular type of exception. For example, if a divide by zero is done in code, then an OverflowException is generated.
Special-purpose exception classes can be found in many namespaces. It is common for an exception class to reside in a namespace with the classes that typically generate the exception. For example, the DataException class is in System.Data, with the ADO.NET components that often generate a DataException instance.
In addition to the dozens of exception types available in the .NET Framework, you can create your own classes that inherit from ApplicationException. This allows you to add custom properties and methods for passing key data related to unexpected events within your application. Of course the next step is to understand how you will reference this class within your applications.
This is a reasonable error message that explains what happened without providing too much detail. While a professional would normally adjust the wording of this message, if this message was presented to a user it wouldn't cause too much of a problem.
On the other hand one of the most brutal ways to get information about an exception is to use the ToString method of the exception. Suppose that you modify the earlier example of IntegerDivide to change the displayed information about the exception, such as using ToString as follows:
Private Function IntegerDivide5(iItems As Integer, iTotal As Integer) As String Dim result As String ' Code that might throw an exception is wrapped in a Try block Try ' This will cause an exception to be thrown if iItems = 0 result = iTotal \ iItems Catch ex As Exception ' If the calculation failed, you get here result = ex.ToString End Try Return result End Function
The message shown in is helpful to a developer because it contains a lot of information, but it's not something you would typically want users to see. Instead, a user normally needs to see a short description of the problem. For the user, a message that looks like string in the Message property is appropriate. On the other hand, what the ToString method returns is, in fact, the concatenation of two properties: the message and the stack trace.
The Source and StackTrace properties provide information regarding where an error occurred. This supplemental information can be useful. In the case of the Source property, the text returns the name of the assembly where the error occurred. You can test this by replacing the ToString method in IntegerDivide5 with the Source property and rerunning the test.
The StackTrace is typically seen as more useful. This property not only returns information about the method where the error occurred, but returns information about the full set of methods used to reach the point where the error occurred. As noted, includes a sample of what is returned when you review this property.
The InnerException property is used to store an exception trail. This comes in handy when multiple exceptions occur. It's quite common for an exception to occur that sets up circumstances whereby further exceptions are raised. As exceptions occur in a sequence, you can choose to stack them for later reference by use of the InnerException property of your Exception object. As each exception joins the stack, the previous Exception object becomes the inner exception in the stack.
For simplicity, we're going to copy the current IntegerDivide5 method to create an inner exception handler in a new method IntegerDivide6. In this exception handler, when the divide by zero error occurs, create a new exception, passing the original exception as part of the constructor. Then in the outer exception handler, unwind your two exceptions displaying both messages.
The following code snippet illustrates the new method IntegerDivide6, and the results of running this new method are shown in .
Private Function IntegerDivide6(iItems As Integer, iTotal As Integer) As String Dim result As String ' Code that might throw an exception is wrapped in a Try block Try Try ' This will cause an exception to be thrown if iItems = 0 result = iTotal \ iItems Catch ex As Exception Dim myException = New Exception( "IntegerDivide6: My Generic Exception", ex) Throw myException End Try Return result Catch ex As Exception ' If the calculation failed, you get here result = "Outer Exception: " & ex.Message & vbCrLf result += "Inner Exception: " & ex.InnerException.Message End Try Return result End Function
shows your custom message as the outer exception. Then the InnerException is referenced and the original error message, the divide-by-zero exception, is displayed.
The GetBaseException method comes in very handy when you are deep in a set of thrown exceptions. This method returns the originating exception by recursively examining the InnerException until it reaches an exception object that has a null InnerException property. That exception is normally the exception that started the chain of unanticipated events.
To illustrate this, modify the code in IntegerDivide6 and replace the original Outer and Inner exception messages with a single line of code as shown in the following code snippet:
'result = "Outer Exception: " & ex.Message & vbCrLf 'result += "Inner Exception: " & ex.InnerException.Message result = "Base Exception: " & ex.GetBaseException.Message
As shown in the code traverses back to the original exception and displays only that message.
The HelpLink property gets or sets the help link for a specific Exception object. It can be set to any string value, but it's typically set to a URL. If you create your own exception in code, you might want to set HelpLink to a URL (or a URN) describing the error in more detail. Then the code that catches the exception can go to that link. You could create and throw your own custom application exception with code like the following:
Dim ex As New ApplicationException("A short description of the problem") ex.HelpLink = "http://mysite.com/somehtmlfile.htm" Throw ex
When trapping an exception, the HelpLink can be used with something like Process.Start to start Internet Explorer using the help link to take the user directly to a help page, where they can see details about the problem.
Method | Description |
Assert | Checks a condition and displays a message if False |
Close | Executes a flush on the output buffer and closes all listeners |
Fail | Emits an error message in the form of an Abort/Retry/Ignore message box |
Flush | Flushes the output buffer and writes it to the listeners |
Write | Writes bytes to the output buffer |
WriteLine | Writes characters followed by a line terminator to the output buffer |
WriteIf | Writes bytes to the output buffer if a specific condition is True |
WriteLineIf | Writes characters followed by a line terminator to the output buffer if a specific condition is True |
The TextWriterTraceListener class is associated with a StreamWriter to output a trace file. In this case, a trace file is a text file, so you need to understand the concepts involved in writing to text files by setting up stream writers. The StreamWriter interface is handled through the System.IO namespace. It enables you to interface with the files in the file system on a given machine.
As you will see, the StreamWriter object opens an output path to a text file, and by binding the StreamWriter object to a listener object you can direct debug output to a text file. lists some of the commonly used methods from the StreamWriter object.
Method | Description |
Close | Closes the StreamWriter. |
Flush | Flushes all content of the StreamWriter to the output file designated upon creation of the StreamWriter. |
Write | Writes byte output to the stream. Optional parameters allow location designation in the stream (offset). |
WriteLine | Writes characters followed by a line terminator to the current stream object. |
The following code snippet shows how you can open an existing file (called TraceResults.txt) for output and assign it to the Listeners object of the Trace object so that it can output your Trace.WriteLine statements.
Friend Shared LogPath As String = "TraceResults.txt" Friend Shared Sub LogMessage(message As String) Dim id As Integer = Trace.Listeners.Add(New TextWriterTraceListener(LogPath)) Try Trace.WriteLine(Now.ToShortDateString & " @ " & Now.ToShortTimeString & " , " & message) Trace.Listeners(id).Flush() Finally Trace.Listeners(id).Close() Trace.Listeners.RemoveAt(id) End Try End Sub
Looking in detail at this code, you first see a declaration of a log path outside of the method. In a production environment, this is typically created as an application configuration setting. This allows for easy review and changes to the location of the resulting file. When no path but only a file name is used, the default behavior will create the file in the running application's default folder.
Next you see a Shared method declaration. The method is shared because there is nothing instance specific associated with it. It can live within a Module if you so desire. The key is it accepts a single parameter, which is the error message that needs to be logged.
The first step is to create a new TextWriterTraceListener and add it to the collection of listener currently associated with your Trace object. This collection can log errors to more than one listener, so if you wanted errors to also be reported to the event log, it is possible to just add multiple listeners. Note however, that because multiple listeners can be used, the final step in this method is to remove the listener which was just added.
Within the Try-Finally block that is then used to output the error, additional information that can be expanded to better classify the message time and/or location can be appended to the message. Once the output is written the buffer is flushed, the file handle is closed, and the Trace object is restored to the state it was in prior to calling this method.
Variations of this simple logging method can be used across multiple different production applications. Over the years the output recorded by such a simple function has proved invaluable in resolving “unexplained” application errors that occurred on remote client machines.
If you have been running the sample code this method was left uncommented specifically so that you could open the TraceResults.txt file and review all of your activity while testing the sample code that is part of this chapter.
This chapter reviewed the Exception object and the syntax available to work with exceptions. You have looked at the various properties of exceptions and learned how to use the exposed information. You have also seen how to promote exceptions to consuming code using the Throw statement.
The chapter also covered writing to Event Logs to capture information about generated exceptions. Event Logs require elevated permissions, and you should use Event Logs judiciously to avoid overloading them.
Regardless of how you capture errors, having information about exceptions captured for after-the-face analysis is an invaluable tool for diagnosis.
The chapter covered a simple technique for generating trace output for programs. By using the full capabilities for exception handling and error logging that are available in Visual Basic, you can make your applications more reliable, and diagnose problems faster when they do occur.
Now that you've covered the core elements of working in Visual Basic, you will shift to the data section. This section starts with memory data like arrays, lists, and dictionaries and then moves on to working with file and database storage.