Chapter
13
Error Handling and Debugging in .NET
Error handling in .NET is quite a bit different than error
handling found in previous versions of VFP. However, both .NET and Visual
FoxPro 8 have try…catch…finally blocks that provide a far more powerful
and flexible way to handle errors in your application. This chapter also
discusses Visual Studio .NET’s debugging tools. As you’ll see, many are similar
to Visual FoxPro’s; some are better, and some are not!
Errors are part and parcel of software applications.
However, in weakly typed languages such as Visual FoxPro, you are more likely
to create code containing bugs than in strongly typed languages that find far
more errors at compile time. However, even the best of compilers can’t find all
of the logic errors you have in your code. In addition, problems can occur at
runtime that have nothing to do with bugs in your code. At times resources are
locked or unavailable, files become corrupt, and remote servers can go down.
Your code needs to be able to handle all of these scenarios.
This is where error handling and debugging tools come to the
rescue. This chapter discusses .NET error handling as well as Visual Studio
.NET’s debugging tools that help you identify errors in your .NET applications.
Common errors vs. exceptions
There are errors and there are exceptions. Errors are
common problems that occur in an application from which the application can
easily recover. For example, a user may enter an invalid value and your
application can display a message warning them of this. A connection to a
database may have been left open and the application can simply close it.
In contrast, exceptions, as suggested by their name, are
uncommon or exceptional errors from which the application cannot recover. For
example, you may not be able to connect to the application’s database—this is
exceptional, because the application is not be able to run properly without
access to the data. A programmer may have passed an invalid value to a
method—this is exceptional (hopefully!) and the method is not able to run.
In the case of common error handling, you should write
defensive code that expects the expected and gracefully handles the problem. In
the case of exceptions, you need to do something more. The following sections
describe exception handling in .NET.
.NET Exceptions
In the .NET world, an exception is an object created
when an error occurs in your application (this is also known as throwing
an exception). The .NET Framework contains hundreds of different exception
classes created when specific types of errors occur. With so many different
exception classes, it’s not practical that you become intimately familiar with
each and every one. Figure 1 shows just a few of the exception classes
you commonly encounter and Table 1 gives a brief description of each of
these classes. As you get more experience writing .NET applications, you learn
about the most common exceptions quickly enough!

Figure 1. Common .NET Exception classes.
Table 1. Common Exception class descriptions
|
Property
|
Description
|
|
System.Exception
|
The base class for all other
exception classes.
|
|
System.ApplicationException
|
The base class for all custom
exception classes you create.
|
|
System.SystemException
|
The base class for all
predefined exceptions in the System namespace.
|
|
System.ArithmeticException
|
The base class for
arithmetic, casting, or conversion operation errors.
|
|
System.OverflowException
|
The exception thrown when
there is an overflow in arithmetic, casting, or conversion operation.
|
|
System.ArgumentException
|
The exception thrown when an
invalid argument is passed to a method.
|
|
System.IndexOutOfRangeException
|
The exception thrown when an
attempt is made to access an array element that is out of the bounds of the array.
|
|
System.StackOverflowException
|
The exception thrown when the
stack overflows with too many pending method calls. This can occur if you
have a recursive, infinite loop in your code.
|
|
System.IO.IOException
|
The base class for all I/O exceptions.
|
|
System.IO.DirectoryNotFoundException
|
The exception thrown when a
directory cannot be found.
|
|
System.IO.FileNotFoundException
|
The exception thrown when a
file cannot be found.
|
Exceptions can be thrown by either the .NET Common Language
Runtime or by an executing application. Some examples of CLR exceptions include
ExecutionEngineException, StackOverflowException, and OutOfMemoryException.
These exceptions are usually of such a critical nature that the application
cannot recover from them. For more information, see the .NET Help topics for
each of these classes.
Catching exceptions with try…catch…finally
.NET provides structured exception handling in the form of try…catch…finally blocks
in both C# and Visual Basic .NET that allow you to catch and respond to
exceptions thrown in your application. These blocks should be used to catch
exceptions—not common errors. Here is a template showing how these try…catch…finally blocks
are structured.
In C#:
try
{
// Contains regular
application code
}
catch
{
// Contains error handling
code
}
finally
{
// Contains any cleanup
code needed to close resources such
// as files and
connections that were opened in the try block
}
And in Visual Basic. NET:
Try
' Contains regular
application code
Catch
' Contains error handling
code
Finally
' Contains any cleanup
code needed to close resources such
' as files and connections
that were opened in the try block
End Try
The following sections explain what the try, catch, and finally blocks are and
how they can be used in your applications.
The try block
You can surround any code that has the potential to
cause an exception with a try
block. A good example is trying to create a connection to a database. For any
number of reasons, including the possibility that the database server is down,
you may encounter an error when you attempt to connect to a database.
The catch block
As mentioned previously, when an error occurs in your
application, a corresponding exception class representing that specific error
is instantiated. The catch
block allows you to get a reference to this exception object and respond
accordingly. The catch
block only executes if an exception occurs.
The catch
block is optional. However, if you
don’t have a catch block, your try block must have an associated finally block, which is discussed in the next
section. If you
don’t have a catch block
associated with a try
block, the runtime searches up the stack to find the next suitable catch block. See the
“Nested try blocks”
section later in this chapter for
details.
The finally block
The finally
block gives you a place to insert cleanup code that is executed whether or not
an error is encountered in the try
block. The finally block,
although useful in many cases, is optional—if you don’t have any specific
cleanup code that needs to be run, you can leave it out. However, if you leave
out the finally block,
your try block must
have an associated catch
block.
A real-world example
Here’s a real-world example showing how to use the try…catch…finally blocks
when accessing data. This method connects to the SQL Server Northwind database
and runs the “Ten Most Expensive Products” stored procedure. The result set is
stored in a data reader that is iterated to build a string and displayed using
the MessageBox class.
In C#:
public void TryCatchDemo()
{
SqlDataReader dr = null;
// Create the connection
string & configure the connection and command objects
string ConnectString =
"server=(local);uid=sa;pwd=;database=NorthWind;";
SqlConnection Conn
= new SqlConnection(ConnectString);
SqlCommand Cmd = new
SqlCommand("Ten Most Expensive Products", Conn);
Cmd.CommandType = CommandType.StoredProcedure;
try
{
// Open the
connection and execute the stored procedure
Conn.Open();
dr =
Cmd.ExecuteReader();
// Get all records in
the result set
string Products =
"";
while (dr.Read())
{
// Access the data
in the current row
Products +=
dr.GetString(0) + ", " + dr.GetDecimal(1) + "\n";
}
MessageBox.Show(Products,
"try...catch...finally demo");
}
catch (Exception e)
{
// Log the Exception
ExceptionLog.Log(e);
}
finally
{
// Close the data
reader and connection
if (dr != null)
{
dr.Close();
}
Conn.Close();
}
}
And in Visual Basic .NET:
Public Sub TryCatchDemo()
Dim dr As SqlDataReader
= Nothing
' Create the connection
string and configure
' the connection and
command objects
Dim ConnectString As
String = _
"server=(local);uid=sa;pwd=;database=NorthWind;"
Dim Conn As New SqlConnection(ConnectString)
Dim Cmd As New
SqlCommand("Ten Most Expensive Products", Conn)
Cmd.CommandType =
CommandType.StoredProcedure
Try
' Open the
connection and execute the stored procedure
Conn.Open()
dr =
Cmd.ExecuteReader()
' Get all records
in the result set
Dim Products As
String = ""
While dr.Read()
' Access the
data in the current row
Products +=
dr.GetString(0) + ", " & _
dr.GetDecimal(1).ToString() & ControlChars.Lf
End While
MessageBox.Show(Products, "try...catch...finally demo")
Catch e As Exception
' Log the Exception
ExceptionLog.LogError(e)
Finally
' Close the data
reader and connection
If Not (dr Is
Nothing) Then
dr.Close()
End If
Conn.Close()
End Try
End Sub 'TryCatchDemo
There are several lines of code at the top of the method
outside of the try…catch…finally
blocks. These lines of code are not trouble spots, so they can safely reside
outside of the try block. Within
the try block is code
with the potential for error. This is where a connection to SQL Server is
opened, the stored procedure is executed, and the result set is processed.
If everything goes smoothly
If no errors are encountered in the try block, the finally block is
executed, which performs cleanup by closing the data reader and the connection
object.
If an error occurs
What if an error occurs (for example, the specified
stored procedure doesn’t exist) on the line of code that executes the stored
procedure?
In C#:
dr =
Cmd.ExecuteReader();
In Visual Basic .NET:
dr =
Cmd.ExecuteReader()
When the error occurs, control is immediately passed to the catch block. Remember the
CLR instantiates an exception object when an error occurs—this is also known as
“throwing an exception”. The catch
block is the mechanism the CLR uses to pass a reference to this object. If you
look at the code, you’ll see the catch
block declares a parameter of the type “Exception” named “e”.
In C#:
catch (Exception e)
In Visual Basic .NET:
Catch e As
Exception
This is how the CLR passes the exception object to the catch block.
Within the catch
block is a single line of code that passes the exception object reference to
the static Log method of the ExceptionLog class (one of your custom sample
classes). This method logs the error for future reference. Check out the
“Logging Errors” section later in this chapter for information on extracting
and logging error information from this exception object.

In reality, if a critical error occurred during an operation
similar to the one in the sample, you probably wouldn’t catch the error at this
level. Depending on the context where the code is called, you may want to let
the client code catch the error instead and respond accordingly.
Remember, if an exception is thrown, the finally method is still
executed. This means the cleanup code that closes the data reader and
connection object is executed after the catch
block is run. This ensures you don’t leave a connection to the database open.
Notice the code performs a check to see if the data reader object dr is null.
In C#:
finally
{
// Close the data
reader and connection
if (dr != null)
{
dr.Close();
}
Conn.Close();
}
And in Visual Basic .NET:
Finally
' Close the data
reader and connection
If Not (dr Is
Nothing) Then
dr.Close()
End If
Conn.Close()
This check is necessary, because if an error occurs while
executing the stored procedure, the data reader may not be returned and the
variable may be null.
After the finally
block is executed, control is passed to the end of the finally
block. This is what you want, because if the stored procedure did not execute
correctly, you don’t want to continue processing. This is different from Visual
FoxPro 7, where the Error event executes and control is passed back to the line
immediately following the code that generated an error.

Visual FoxPro 8 introduces Try…Catch structured exception
handling to the FoxPro language. For details, see the VFP 8 Help file.
Catching specific exceptions
The previous example shows how you can use the catch block to catch all
exceptions. However, there may be times where you would like to catch specific
exceptions and handle them differently. The following code sample shows how to
do this.
In C#:
public void
CatchOverFlow()
{
long
MyLong = 3000000000;
int
MyInteger;
try
{
// Store the value of MyDouble into MyInteger
checked
{
MyInteger = (int)MyLong;
}
}
catch(OverflowException
e)
{
// Do additional processing…
MessageBox.Show("Caught an arithmetic
overflow exception.", "Catch demo");
// Catch a specific exception
ExceptionLog.LogError(e);
}
catch(Exception
e)
{
// Catch the generic exceptions
ExceptionLog.LogError(e);
}
finally
{
// Cleanup code
}
}
In Visual Basic .NET:
Public Sub CatchOverFlow()
Dim MyLong As Long =
3000000000
Dim MyInteger As
Integer
Try
' Store the value
of MyDouble into MyInteger
MyInteger =
CInt(MyLong)
Catch e As
OverflowException
' Do additional
processing
MessageBox.Show("Caught an arithmetic overflow exception.",
"Catch demo")
' Catch a specific
exception
ExceptionLog.LogError(e)
Catch e As Exception
' Catch the generic
exceptions
ExceptionLog.LogError(e)
Finally
' Cleanup code
End Try
End Sub 'CatchOverFlow
The code within the try
block takes the value of MyLong and casts it to an integer. However, since the
largest positive value an integer can hold is a 2,147,483,647, this code
generates an exception.
Notice the C# code contains a checked
block surrounding the code that stores the value of MyLong into MyInteger, but
the Visual Basic .NET code does not:
checked
{
MyInteger = (int)MyLong;
}
In C#, an arithmetic overflow condition does not
automatically cause an exception to be thrown. If you want an exception to be
thrown, you need to surround the code with a checked
block. In Visual Basic .NET, you don’t have a choice—an exception is always
thrown when an overflow condition is encountered. Alternately, you can tell the
C# compiler you want all code checked by using the “/checked” compiler option.
Notice there are two catch
blocks in this code. The first catch
block is specifically checking for an OverflowException error and the second
block is checking for a generic Exception. If code in the try block generates an
exception other than an OverflowException, it is caught by the second catch block.
The OverflowException catch
block has a placeholder for “Do additional processing”. In the real world, you
could change the default exception message to something more informative. The
technique for doing this is discussed later in this chapter under the
“System.Exception” section.
Catching all exceptions
If an exception is not caught in a catch block, the Common
Language Runtime catches it
for you. If this happens, an un-user friendly message is displayed and
your program terminates. The best way to avoid a situation where your code does
not catch an error is to
use nested try blocks.
Nested try blocks
Nested try
blocks allow you to bracket one try
block with another that sits at a higher level. Here is some sample code
demonstrating how this works.
In C#:
public void NestedTry()
{
try
{
try
{
int x;
int y = 0;
// Divide 100 by 0
(throws an exception)
x = 100 / y;
}
catch
(OverflowException e)
{
ExceptionLog.LogError(e);
}
finally
{
// cleanup
}
}
catch (Exception e)
{
ExceptionLog.LogError(e);
}
finally
{
// cleanup
}
}
And in Visual Basic .NET:
Public Sub NestedTry()
Try
Try
Dim x As
Integer
Dim y As
Integer = 0
' Divide 100 by
0 (throws an exception)
x = CInt(100 / y)
Catch e As
OverflowException
ExceptionLog.LogError(e)
Finally
' cleanup
MessageBox.Show("Running the inner finally block")
End Try
Catch e As Exception
ExceptionLog.LogError(e)
Finally
' cleanup
End Try
End Sub 'NestedTry
The inner try
block contains code that divides 100 by the variable y,
which contains the value zero. Running this code throws a
DivideByZeroException. Notice, however, the inner catch
block only checks for an OverflowException. In this situation, the .NET runtime
determines there is no catch
block for the exception that was thrown and does the following:
1. Runs
the inner finally block
associated with the inner catch.
2. Searches
for a suitable catch block
further up the stack.
3. When
it finds the outer catch
block that checks for any exception, it executes the code within it.
4. Executes
the outer finally block.
Although this sample code nests try
blocks within a single method, you can nest them
at any level in your application calling chain. In fact, it’s a good idea to
bracket your entire application in a try
block as a final check for any unhandled exceptions within
your application.
For example, in C#:
static void Main()
{
try
{
Application.Run(new MainAppWindow());
}
catch (Exception e)
{
ExceptionLog.LogError(e);
}
finally
{
// Cleanup
}
}
And in Visual Basic .NET:
Shared Sub Main()
Try
Application.Run(New
MainAppWindow())
Catch e As Exception
ExceptionDisplay.ShowError(e)
ExceptionLog.LogError(e)
Finally
' cleanup
End Try
End Sub
With your application bracketed this
way, you can be sure that all uncaught exceptions are intercepted and logged. However,
if an exception is not caught until it gets to this point, the application
stops executing. In contrast, if you catch an exception at the point it occurs
(or some layer between the actual error and the topmost try…catch…finally block),
then application execution passes to the associated finally
block and application
execution continues.
At times, you may not know how to handle an exception at the
level where the error occurs. For example, you may have a library of classes
used by a variety of applications. Because you don’t know the context where a
particular class is used, you often don’t know how to properly respond to
exceptions. In cases like this, you should allow the calling code to handle any
exceptions that may occur. However, you may still want to catch generic
exceptions and throw your own custom exception objects to provide the calling
code with more specific information.
Throwing your own exceptions
Not all exceptions are automatically generated by the
.NET runtime. Some are the result of perfectly valid .NET code that simply
won’t work. For example, the contents of a file may have become corrupt, a
particular file may not exist, or it may be locked. If you detect error
conditions within your application, you can manually throw exceptions from
within your code.
For example, the following class contains a method called
IsBirthMonth, which accepts an integer and determines if it is my birth month
(October). The code checks to see if the value of the month
parameter is between 1 and 12. If it’s not, an exception is thrown—specifically
an ArgumentOutOfRangeException, which is one of the .NET Framework’s exception
classes.
In C#:
public class
ThrowExceptionDemo
{
public bool IsBirthMonth(int month)
{
bool BirthMonth = false;
if (! (month >= 1 && month <= 12))
{
throw new ArgumentOutOfRangeException();
}
if (month == 10)
{
BirthMonth = true;
}
return BirthMonth;
}
}
In Visual Basic .NET:
Public Class ThrowExceptionDemo
Public Function IsBirthMonth(month
As Integer) As Boolean
Dim BirthMonth As
Boolean = False
If Not(month >= 1
And month <= 12) Then
Throw New
ArgumentOutOfRangeException()
End If
If month = 10 Then
BirthMonth = True
End If
Return BirthMonth
End Function
'IsBirthMonth
End Class 'ThrowExceptionDemo
If you have an application-level try…catch…finally
block in place, the block would catch this exception.
Displaying and Logging Errors
Up to this point I’ve glossed over how to respond to
exceptions that are caught by simply calling ExceptionLog.LogError. In this
section I’ll show you how to retrieve error information from an Exception
object and display or log the error. Before doing this, first take a closer
look at the System.Exception class.
System.Exception
As mentioned previously, all exception classes—both the
.NET Framework classes and your own custom exceptions—are derived from the
System.Exception class. Table 2 contains a list of the Exception class’s
public properties with a brief description of each.
Table 2. System.Exception properties
|
Property
|
Description
|
|
HelpLink
|
Specifies a Uniform Resource
Name (URN) or Uniform Resource Locater (URL) link to the Help file associated
with the exception.
Example:
“file:///C:/Applications/MyHelp.html#MyError1
|
|
Message
|
The text of the error
message.
|
|
Source
|
Specifies the name of the
object that caused the error.
|
|
TargetSite
|
Specifies the method that
threw the exception.
|
|
StackTrace
|
Contains a string detailing
the call stack at the time the exception occurred.
|
|
InnerException
|
Specifies a previous
exception that caused the current exception. If there is no previous
exception, this property is null.
|
One of the most important properties of the Exception object
is Message. This property contains the text of the error message. The .NET
runtime supplies a default message that is stored in this property. For
example, the default message displayed when the IsBirthMonth method throws its
exception is:
"Specified argument was out of the range of valid
values."
You can override this default message by specifying a
different message in the constructor of the exception class. The
ArgumentOutOfRangeException class has four different constructors you can
choose. The constructor signature I’ve selected in this example expects three
parameters:
·
Parameter Name
·
Actual Value
·
Message
Here’s the modified code in C#:
throw new ArgumentOutOfRangeException("month",
month,"Values
for month must be between 1-12");
And in Visual Basic .NET:
Throw New ArgumentOutOfRangeException("month",
_
month,
"Values for month must be between 1-12")
Now when this exception is thrown, the default message is:
" Values for month must be between 1-12."
This is much more descriptive than the generic “argument out
of range” message.
Displaying exception information
To see the value of an exception object’s message
property (and other properties), the following sample code uses a class with a static
method named “ShowError”.
In C#:
public class ExceptionDisplay
{
public static void
ShowError(Exception e)
{
MessageBox.Show("Error
information\n\n" +
"Message: " + e.Message + "\n"
+
"Source: " + e.Source +
"\n" +
"TargetSite: " + e.TargetSite
+ "\n" +
"Stack trace:
" + e.StackTrace,
"Exception
demo");
}
}
And in Visual Basic .NET:
Public Class ExceptionDisplay
Public Shared Sub
ShowError(ByVal e As Exception)
MessageBox.Show("Message: " & e.Message &
ControlChars.Lf & _
ControlChars.Lf
& "Source: " & e.Source & ControlChars.Lf & _
ControlChars.Lf
& "TargetSite: " & e.TargetSite.ToString() & _
ControlChars.Lf
& ControlChars.Lf & "Stack trace: " & _
ControlChars.Lf
& e.StackTrace, "Exception demo")
End Sub 'ShowError
End Class 'ExceptionDisplay
Here is a modified version of the IsBirthMonth method
containing a catch block that calls this new static method.
In C#:
public bool
IsBirthMonth(int month)
{
bool BirthMonth = false;
try
{
if (! (month >= 1 && month <= 12))
{
throw new
ArgumentOutOfRangeException("month",
month,"Values
for month must be between 1-12");
}
}
catch (ArgumentOutOfRangeException e)
{
ExceptionDisplay.ShowError(e);
}
finally
{
if (month == 10)
{
BirthMonth = true;
}
}
return BirthMonth;
}
And in Visual Basic .NET:
Public Function IsBirthMonth(ByVal month As Integer) As
Boolean
Dim BirthMonth As
Boolean = False
Try
If Not (month >=
1 And month <= 12) Then
Throw New
ArgumentOutOfRangeException("month", _
month,
"Values for month must be between 1-12")
End If
Catch e As
ArgumentOutOfRangeException
ExceptionDisplay.ShowError(e)
ExceptionLog.LogError(e)
Finally
If month = 10 Then
BirthMonth =
True
End If
End Try
Return BirthMonth
End Function 'IsBirthMonth
If you run this code from the book’s sample application,
you’ll see the message box shown in Figure 2.

Figure 2. Important information can be gleaned
from the properties of an exception object.
As you can see, the Message property contains a three-part
string that includes the error message, the parameter name, and the actual value,
each shown on their own line.
If you do not set the Source property, it defaults to the
name of the assembly where the error occurred—in this case, “HW .Net Book
Samples_CSharp”, or if you’re using Visual Basic .NET, “HW .Net Book
Samples_Visual Basic”.
The TargetSite property contains a string specifying the
method that threw the exception, in this case “IsBirthMonth”. As shown, it also
includes the type of the parameter and the type of the return value.
The StackTrace property provides information regarding the
methods in execution at the time the exception is thrown. As seen in Figure 2,
the example only lists a single class and method—the ThrowExceptionDemo class
and the IsBirthMonth method. It also lists the source code file and line number
where the error was thrown.
Logging exception information
Typically, you should create a way to save exception
information to some type of log file. In this section, you’ll see a few easy
ways to do this.
Figure 3 contains a UML class diagram of an
ExceptionLog class. This is a custom class found in this chapter’s sample code
that allows you to save exception information to an XML log file. Saving
exceptions as XML gives you the ability to easily read and manipulate the
exception data using either .NET run time or design time tools.

Figure 3. The ExceptionLog class found in this
chapter’s sample code provides a means to save errors to a log file.
The ExceptionLog’s LogFile property specifies the name of the
XML log file. The ApplicationName property identifies the name of the
application that generated the exception. Specifying the name of the
application lets you save exception data from multiple applications into a
single exception log.
The ExceptionLog’s CreateLogFile method creates a new log
file with the name given in the LogFile property.
Here is the code for this method in C#:
public static void CreateLogFile()
{
// Create an
XmlTextWriter, specifying the name of the new XML log file
XmlTextWriter xtw = new
XmlTextWriter(LogFile, null);
// Write the XML
declaration at the top of the file
xtw.WriteStartDocument();
// Add a comment to the
file indicating the date/time created
xtw.WriteComment("Log
file created: " + DateTime.Now);
// Add an empty
<EventLog> element
xtw.WriteStartElement("EventLog");
xtw.WriteEndElement();
// Close the stream
xtw.Close();
}
And in Visual Basic .NET:
Public Shared Sub CreateLogFile()
' Create an
XmlTextWriter, specifying the name of the new XML log file
Dim xtw As New
XmlTextWriter(LogFile, Nothing)
' Write the XML
declaration at the top of the file
xtw.WriteStartDocument()
' Add a comment to the
file indicating the date/time created
xtw.WriteComment(("Log file created: " +
DateTime.Now.ToString()))
' Add an empty
<EventLog> element
xtw.WriteStartElement("EventLog")
xtw.WriteEndElement()
' Close the stream
xtw.Close()
End Sub 'CreateLogFile
This method uses an XmlTextWriter to create the new XML log
file, and then writes:
·
The XML
declaration at the top of the file.
·
A comment indicating the date and time the file
was created.
·
An empty <EventLog> element.
For more information about
using the XmlTextWriter class and other XML classes referenced in this chapter,
see Chapter 11, “.NET XML”.
Now take a look at the code in the ExceptionLog’s LogError
method.
In C#:
public static void LogError(Exception e)
{
// If the log file doesn't
exist, create it
if (!File.Exists(LogFile))
{
CreateLogFile();
}
// Open the log file
XmlDocument XmlDoc = new
XmlDocument();
XmlDoc.Load(LogFile);