Understanding Structured Exception Handling - Object-Oriented Programming with C# - C# 6.0 and the .NET 4.6 Framework (2015)

C# 6.0 and the .NET 4.6 Framework (2015)

PART III

image

Object-Oriented Programming with C#

CHAPTER 7

image

Understanding Structured Exception Handling

In this chapter, you will learn how to handle runtime anomalies in your C# code through the use of structured exception handling. Not only will you examine the C# keywords that allow you to handle such matters (try, catch, throw, finally, when), but you will also come to understand the distinction between application-level and system-level exceptions, as well as the role of the System.Exception base class. This discussion will lead into the topic of building custom exceptions and, finally, to a quick look at some exception-centric debugging tools of Visual Studio.

Ode to Errors, Bugs, and Exceptions

Despite what our (sometimes inflated) egos may tell us, no programmer is perfect. Writing software is a complex undertaking, and given this complexity, it is quite common for even the best software to ship with various problems. Sometimes the problem is caused by bad code (such as overflowing the bounds of an array). Other times, a problem is caused by bogus user input that has not been accounted for in the application’s code base (e.g., a phone number input field assigned to the value Chucky). Now, regardless of the cause of the problem, the end result is that the application does not work as expected. To help frame the upcoming discussion of structured exception handling, allow me to provide definitions for three commonly used anomaly-centric terms.

· Bugs: These are, simply put, errors made by the programmer. For example, suppose you are programming with unmanaged C++. If you fail to delete dynamically allocated memory, resulting in a memory leak, you have a bug.

· User errors: User errors, on the other hand, are typically caused by the individual running your application, rather than by those who created it. For example, an end user who enters a malformed string into a text box could very well generate an error if you fail to handle this faulty input in your code base.

· Exceptions: Exceptions are typically regarded as runtime anomalies that are difficult, if not impossible, to account for while programming your application. Possible exceptions include attempting to connect to a database that no longer exists, opening a corrupted XML file, or trying to contact a machine that is currently offline. In each of these cases, the programmer (or end user) has little control over these “exceptional” circumstances.

Given these definitions, it should be clear that .NET structured exception handling is a technique for dealing with runtime exceptions. However, even for the bugs and user errors that have escaped your view, the CLR will often generate a corresponding exception that identifies the problem at hand. By way of a few examples, the .NET base class libraries define numerous exceptions, such as FormatException, IndexOutOfRangeException, FileNotFoundException, ArgumentOutOfRangeException, and so forth.

Within the .NET nomenclature, an exception accounts for bugs, bogus user input, and runtime errors, even though programmers may view each of these as a distinct issue. However, before I get too far ahead of myself, let’s formalize the role of structured exception handling and check out how it differs from traditional error-handling techniques.

Image Note To make the code examples used in this book as clean as possible, I will not catch every possible exception that may be thrown by a given method in the base class libraries. In your production-level projects, you should, of course, make liberal use of the techniques presented in this chapter.

The Role of .NET Exception Handling

Prior to .NET, error handling under the Windows operating system was a confused mishmash of techniques. Many programmers rolled their own error-handling logic within the context of a given application. For example, a development team could define a set of numerical constants that represented known error conditions and make use of them as method return values. By way of an example, consider the following partial C code:

/* A very C-style error trapping mechanism. */
#define E_FILENOTFOUND 1000

int UseFileSystem()
{
// Assume something happens in this function
// that causes the following return value.
return E_FILENOTFOUND;
}

void main()
{
int retVal = UseFileSystem();
if(retVal == E_FILENOTFOUND)
printf("Cannot find file...");
}

This approach is less than ideal, given the fact that the constant E_FILENOTFOUND is little more than a numerical value and is far from being a helpful agent regarding how to deal with the problem. Ideally, you would like to wrap the error’s name, a descriptive message, and other helpful information about this error condition into a single, well-defined package (which is exactly what happens under structured exception handling). In addition to a developer’s ad hoc techniques, the Windows API defines hundreds of error codes that come by way of #defines,HRESULTs, and far too many variations on the simple Boolean (bool, BOOL, VARIANT_BOOL, and so on).

The obvious problem with these older techniques is the tremendous lack of symmetry. Each approach is more or less tailored to a given technology, a given language, and perhaps even a given project. To put an end to this madness, the .NET platform provides a standard technique to send and trap runtime errors: structured exception handling. The beauty of this approach is that developers now have a unified approach to error handling, which is common to all languages targeting the .NET platform. Therefore, the way in which a C# programmer handles errors is syntactically similar to that of a VB programmer, or a C++ programmer using C++/CLI.

As an added bonus, the syntax used to throw and catch exceptions across assemblies and machine boundaries is identical. For example, if you use C# to build a Windows Communication Foundation (WCF) service, you can throw a SOAP fault to a remote caller, using the same keywords that allow you to throw an exception between methods in the same application.

Another bonus of .NET exceptions is that rather than receiving a cryptic numerical value, exceptions are objects that contain a human-readable description of the problem, as well as a detailed snapshot of the call stack that triggered the exception in the first place. Furthermore, you are able to give the end user help-link information that points the user to a URL that provides details about the error, as well as custom programmer-defined data.

The Building Blocks of .NET Exception Handling

Programming with structured exception handling involves the use of four interrelated entities.

· A class type that represents the details of the exception

· A member that throws an instance of the exception class to the caller under the correct circumstances

· A block of code on the caller’s side that invokes the exception-prone member

· A block of code on the caller’s side that will process (or catch) the exception, should it occur

The C# programming language offers five keywords (try, catch, throw, finally, and when) that allow you to throw and handle exceptions. The object that represents the problem at hand is a class extending System.Exception (or a descendent thereof). Given this fact, let’s check out the role of this exception- centric base class.

The System.Exception Base Class

All exceptions ultimately derive from the System.Exception base class, which in turn derives from System.Object. Here is the crux of this class (note that some of these members are virtual and may thus be overridden by derived classes):

public class Exception : ISerializable, _Exception
{
// Public constructors
public Exception(string message, Exception innerException);
public Exception(string message);
public Exception();
...

// Methods
public virtual Exception GetBaseException();
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context);

// Properties
public virtual IDictionary Data { get; }
public virtual string HelpLink { get; set; }
public Exception InnerException { get; }
public virtual string Message { get; }
public virtual string Source { get; set; }
public virtual string StackTrace { get; }
public MethodBase TargetSite { get; }
...
}

As you can see, many of the properties defined by System.Exception are read-only in nature. This is because derived types will typically supply default values for each property. For example, the default message of the IndexOutOfRangeException type is “Index was outside the bounds of the array.”

Image Note The Exception class implements two .NET interfaces. Although you have yet to examine interfaces (see Chapter 8), just understand that the _Exception interface allows a .NET exception to be processed by an unmanaged code base (such as a COM application), while the ISerializable interface allows an exception object to be persisted across boundaries (such as a machine boundary).

Table 7-1 describes the most important members of System.Exception.

Table 7-1. Core Members of the System.Exception Type

System.Exception Property

Meaning in Life

Data

This read-only property retrieves a collection of key/value pairs (represented by an object implementing IDictionary) that provide additional, programmer-defined information about the exception. By default, this collection is empty.

HelpLink

This property gets or sets a URL to a help file or web site describing the error in full detail.

InnerException

This read-only property can be used to obtain information about the previous exception(s) that caused the current exception to occur. The previous exception(s) are recorded by passing them into the constructor of the most current exception.

Message

This read-only property returns the textual description of a given error. The error message itself is set as a constructor parameter.

Source

This property gets or sets the name of the assembly, or the object, that threw the current exception.

StackTrace

This read-only property contains a string that identifies the sequence of calls that triggered the exception. As you might guess, this property is useful during debugging or if you want to dump the error to an external error log.

TargetSite

This read-only property returns a MethodBase object, which describes numerous details about the method that threw the exception (invoking ToString() will identify the method by name).

The Simplest Possible Example

To illustrate the usefulness of structured exception handling, you need to create a class that will throw an exception under the correct (or one might say exceptional) circumstances. Assume you have created a new C# Console Application project (named SimpleException) that defines two class types (Car and Radio) associated by the “has-a” relationship. The Radio type defines a single method that turns the radio’s power on or off.

class Radio
{
public void TurnOn(bool on)
{
if(on)
Console.WriteLine("Jamming...");
else
Console.WriteLine("Quiet time...");
}
}

In addition to leveraging the Radio class via containment/delegation, the Car class (shown next) is defined in such a way that if the user accelerates a Car object beyond a predefined maximum speed (specified using a constant member variable named MaxSpeed), its engine explodes, rendering the Car unusable (captured by a private bool member variable named carIsDead).

Beyond these points, the Car type has a few properties to represent the current speed and a user supplied “pet name,” as well as various constructors to set the state of a new Car object. Here is the complete definition (with code comments):

class Car
{
// Constant for maximum speed.
public const int MaxSpeed = 100;

// Car properties.
public int CurrentSpeed {get; set;} = 0;
public string PetName {get; set;} = "";

// Is the car still operational?
private bool carIsDead;

// A car has-a radio.
private Radio theMusicBox = new Radio();

// Constructors.
public Car() {}
public Car(string name, int speed)
{
CurrentSpeed = speed;
PetName = name;
}

public void CrankTunes(bool state)
{
// Delegate request to inner object.
theMusicBox.TurnOn(state);
}

// See if Car has overheated.
public void Accelerate(int delta)
{
if (carIsDead)
Console.WriteLine("{0} is out of order...", PetName);
else
{
CurrentSpeed += delta;
if (CurrentSpeed > MaxSpeed)
{
Console.WriteLine("{0} has overheated!", PetName);
CurrentSpeed = 0;
carIsDead = true;
}
else
Console.WriteLine("=> CurrentSpeed = {0}", CurrentSpeed);
}
}
}

Now, if you implement a Main() method that forces a Car object to exceed the predefined maximum speed (set to 100, in the Car class) as shown here:

static void Main(string[] args)
{
Console.WriteLine("***** Simple Exception Example *****");
Console.WriteLine("=> Creating a car and stepping on it!");
Car myCar = new Car("Zippy", 20);
myCar.CrankTunes(true);

for (int i = 0; i < 10; i++)
myCar.Accelerate(10);
Console.ReadLine();
}

you would see the following output:

***** Simple Exception Example *****
=> Creating a car and stepping on it!
Jamming...
=> CurrentSpeed = 30
=> CurrentSpeed = 40
=> CurrentSpeed = 50
=> CurrentSpeed = 60
=> CurrentSpeed = 70
=> CurrentSpeed = 80
=> CurrentSpeed = 90
=> CurrentSpeed = 100
Zippy has overheated!
Zippy is out of order...

Throwing a General Exception

Now that you have a functional Car class, I’ll demonstrate the simplest way to throw an exception. The current implementation of Accelerate() simply displays an error message if the caller attempts to speed up the Car beyond its upper limit.

To retrofit this method to throw an exception if the user attempts to speed up the automobile after it has met its maker, you want to create and configure a new instance of the System.Exception class, setting the value of the read-only Message property via the class constructor. When you want to send the exception object back to the caller, use the C# throw keyword. Here is the relevant code update to the Accelerate() method:

// This time, throw an exception if the user speeds up beyond MaxSpeed.
public void Accelerate(int delta)
{
if (carIsDead)
Console.WriteLine("{0} is out of order...", PetName);
else
{
CurrentSpeed += delta;
if (CurrentSpeed >= MaxSpeed)
{
carIsDead = true;
CurrentSpeed = 0;

// Use the "throw" keyword to raise an exception.
throw new Exception(string.Format("{0} has overheated!", PetName));
}
else
Console.WriteLine("=> CurrentSpeed = {0}", CurrentSpeed);
}
}

Before examining how a caller would catch this exception, let’s look at a few points of interest. First, when you are throwing an exception, it is always up to you to decide exactly what constitutes the error in question and when an exception should be thrown. Here, you are making the assumption that if the program attempts to increase the speed of a Car object beyond the maximum, a System.Exception object should be thrown to indicate the Accelerate() method cannot continue (which may or may not be a valid assumption; this will be a judgment call on your part based on the application you are creating).

Alternatively, you could implement Accelerate() to recover automatically without needing to throw an exception in the first place. By and large, exceptions should be thrown only when a more terminal condition has been met (for example, not finding a necessary file, failing to connect to a database, and the like). Deciding exactly what justifies throwing an exception is a design issue you must always contend with. For the current purposes, assume that asking a doomed automobile to increase its speed is cause to throw an exception.

In any case, if you were to rerun the application at this point using the previous logic in Main(), the exception will eventually be thrown. As shown in the following output, the result of not handling this error is less than ideal, given you receive a verbose error dump followed by the program’s termination:

***** Simple Exception Example *****
=> Creating a car and stepping on it!
Jamming...
=> CurrentSpeed = 30
=> CurrentSpeed = 40
=> CurrentSpeed = 50
=> CurrentSpeed = 60
=> CurrentSpeed = 70
=> CurrentSpeed = 80
=> CurrentSpeed = 90

Unhandled Exception: System.Exception: Zippy has overheated!
at SimpleException.Car.Accelerate(Int32 delta) in C:\MyBooks\C# Book (7th ed)
\Code\Chapter_7\SimpleException\Car.cs:line 62
at SimpleException.Program.Main(String[] args) in C:\MyBooks\C# Book (7th ed)
\Code\Chapter_7\SimpleException\Program.cs:line 20
Press any key to continue . . .

Catching Exceptions

Image Note For those coming to .NET from a Java background, understand that type members are not prototyped with the set of exceptions they may throw (in other words, .NET does not support checked exceptions). For better or for worse, you are not required to handle every exception thrown from a given member.

Because the Accelerate() method now throws an exception, the caller needs to be ready to handle the exception, should it occur. When you are invoking a method that may throw an exception, you make use of a try/catch block. After you have caught the exception object, you are able to invoke the members of the exception object to extract the details of the problem.

What you do with this data is largely up to you. You might want to log this information to a report file, write the data to the Windows event log, e-mail a system administrator, or display the problem to the end user. Here, you will simply dump the contents to the console window:

// Handle the thrown exception.
static void Main(string[] args)
{
Console.WriteLine("***** Simple Exception Example *****");
Console.WriteLine("=> Creating a car and stepping on it!");
Car myCar = new Car("Zippy", 20);
myCar.CrankTunes(true);

// Speed up past the car’s max speed to
// trigger the exception.
try
{
for(int i = 0; i < 10; i++)
myCar. Accelerate(10);
}
catch(Exception e)
{
Console.WriteLine("\n*** Error! ***");
Console.WriteLine("Method: {0}", e.TargetSite);
Console.WriteLine("Message: {0}", e.Message);
Console.WriteLine("Source: {0}", e.Source);
}

// The error has been handled, processing continues with the next statement.
Console.WriteLine("\n***** Out of exception logic *****");
Console.ReadLine();
}

In essence, a try block is a section of statements that may throw an exception during execution. If an exception is detected, the flow of program execution is sent to the appropriate catch block. On the other hand, if the code within a try block does not trigger an exception, the catchblock is skipped entirely, and all is right with the world. The following output shows a test run of this program:

***** Simple Exception Example *****
=> Creating a car and stepping on it!
Jamming...
=> CurrentSpeed = 30
=> CurrentSpeed = 40
=> CurrentSpeed = 50
=> CurrentSpeed = 60
=> CurrentSpeed = 70
=> CurrentSpeed = 80
=> CurrentSpeed = 90

*** Error! ***
Method: Void Accelerate(Int32)
Message: Zippy has overheated!
Source: SimpleException

***** Out of exception logic *****

As you can see, after an exception has been handled, the application is free to continue on from the point after the catch block. In some circumstances, a given exception could be critical enough to warrant the termination of the application. However, in a good number of cases, the logic within the exception handler will ensure the application can continue on its merry way (although it could be slightly less functional, such as not being able to connect to a remote data source).

Configuring the State of an Exception

Currently, the System.Exception object configured within the Accelerate() method simply establishes a value exposed to the Message property (via a constructor parameter). As shown previously in Table 7-1, however, the Exception class also supplies a number of additional members (TargetSite, StackTrace, HelpLink, and Data) that can be useful in further qualifying the nature of the problem. To spruce up the current example, let’s examine further details of these members on a case-by-case basis.

The TargetSite Property

The System.Exception.TargetSite property allows you to determine various details about the method that threw a given exception. As shown in the previous Main() method, printing the value of TargetSite will display the return type, name, and parameter types of the method that threw the exception. However, TargetSite does not return just a vanilla-flavored string but rather a strongly typed System.Reflection.MethodBase object. This type can be used to gather numerous details regarding the offending method, as well as the class that defines the offending method. To illustrate, assume the previous catch logic has been updated as follows:

static void Main(string[] args)
{
...
// TargetSite actually returns a MethodBase object.
catch(Exception e)
{
Console.WriteLine("\n*** Error! ***");
Console.WriteLine("Member name: {0}", e.TargetSite);
Console.WriteLine("Class defining member: {0}",
e.TargetSite.DeclaringType);
Console.WriteLine("Member type: {0}", e.TargetSite.MemberType);
Console.WriteLine("Message: {0}", e.Message);
Console.WriteLine("Source: {0}", e.Source);
}
Console.WriteLine("\n***** Out of exception logic *****");
Console.ReadLine();
}

This time, you make use of the MethodBase.DeclaringType property to determine the fully qualified name of the class that threw the error (SimpleException.Car, in this case) as well as the MemberType property of the MethodBase object to identify the type of member (such as a property versus a method) where this exception originated. In this case, the catch logic would display the following:

*** Error! ***
Member name: Void Accelerate(Int32)
Class defining member: SimpleException.Car
Member type: Method
Message: Zippy has overheated!
Source: SimpleException

The StackTrace Property

The System.Exception.StackTrace property allows you to identify the series of calls that resulted in the exception. Be aware that you never set the value of StackTrace, as it is established automatically at the time the exception is created. To illustrate, assume you have once again updated your catch logic.

catch(Exception e)
{
...
Console.WriteLine("Stack: {0}", e.StackTrace);
}

If you were to run the program, you would find the following stack trace is printed to the console (your line numbers and file paths may differ, of course):

Stack: at SimpleException.Car.Accelerate(Int32 delta)
in c:\MyApps\SimpleException\car.cs:line 65 at SimpleException.Program.Main()
in c:\MyApps\SimpleException\Program.cs:line 21

The string returned from StackTrace documents the sequence of calls that resulted in the throwing of this exception. Notice how the bottommost line number of this string identifies the first call in the sequence, while the topmost line number identifies the exact location of the offending member. Clearly, this information can be quite helpful during the debugging or logging of a given application, as you are able to “follow the flow” of the error’s origin.

The HelpLink Property

While the TargetSite and StackTrace properties allow programmers to gain an understanding of a given exception, this information is of little use to the end user. As you have already seen, the System.Exception.Message property can be used to obtain human-readable information that can be displayed to the current user. In addition, the HelpLink property can be set to point the user to a specific URL or standard Windows help file that contains more detailed information.

By default, the value managed by the HelpLink property is an empty string. If you want to fill this property with a more interesting value, you need to do so before throwing the System.Exception object. Here are the relevant updates to the Car.Accelerate() method:

public void Accelerate(int delta)
{
if (carIsDead)
Console.WriteLine("{0} is out of order...", PetName);
else
{
CurrentSpeed += delta;
if (CurrentSpeed >= MaxSpeed)
{
carIsDead = true;
CurrentSpeed = 0;

// We need to call the HelpLink property, thus we need to
// create a local variable before throwing the Exception object.
Exception ex =
new Exception(string.Format("{0} has overheated!", PetName));
ex.HelpLink = "http://www.CarsRUs.com";
throw ex;
}
else
Console.WriteLine("=> CurrentSpeed = {0}", CurrentSpeed);
}
}

The catch logic could now be updated to print this help link information as follows:

catch(Exception e)
{
...
Console.WriteLine("Help Link: {0}", e.HelpLink);
}

The Data Property

The Data property of System.Exception allows you to fill an exception object with relevant auxiliary information (such as a timestamp). The Data property returns an object implementing an interface named IDictionary, defined in the System.Collections namespace.Chapter 8 examines the role of interface-based programming, as well as the System.Collections namespace. For the time being, just understand that dictionary collections allow you to create a set of values that are retrieved using a specific key. Observe the next update to theCar.Accelerate() method:

public void Accelerate(int delta)
{
if (carIsDead)
Console.WriteLine("{0} is out of order...", PetName);
else
{
CurrentSpeed += delta;
if (CurrentSpeed >= MaxSpeed)
{
carIsDead = true;
CurrentSpeed = 0;

// We need to call the HelpLink property, thus we need
// to create a local variable before throwing the Exception object.
Exception ex =
new Exception(string.Format("{0} has overheated!", PetName));
ex.HelpLink = "http://www.CarsRUs.com";

// Stuff in custom data regarding the error.
ex.Data.Add("TimeStamp",
string.Format("The car exploded at {0}", DateTime.Now));
ex.Data.Add("Cause", "You have a lead foot.");
throw ex;
}
else
Console.WriteLine("=> CurrentSpeed = {0}", CurrentSpeed);
}
}

To successfully enumerate over the key-value pairs, you must first make sure to specify a using directive for the System.Collections namespace, since you will use a DictionaryEntry type in the file containing the class implementing your Main() method.

using System.Collections;

Next, you need to update the catch logic to test that the value returned from the Data property is not null (the default value). After that, you use the Key and Value properties of the DictionaryEntry type to print the custom data to the console.

catch (Exception e)
{
...
Console.WriteLine("\n-> Custom Data:");
foreach (DictionaryEntry de in e.Data)
Console.WriteLine("-> {0}: {1}", de.Key, de.Value);
}

With this, here’s the final output you’d see:

***** Simple Exception Example *****
=> Creating a car and stepping on it!
Jamming...
=> CurrentSpeed = 30
=> CurrentSpeed = 40
=> CurrentSpeed = 50
=> CurrentSpeed = 60
=> CurrentSpeed = 70
=> CurrentSpeed = 80
=> CurrentSpeed = 90

*** Error! ***
Member name: Void Accelerate(Int32)
Class defining member: SimpleException.Car
Member type: Method
Message: Zippy has overheated!
Source: SimpleException
Stack: at SimpleException.Car.Accelerate(Int32 delta)
at SimpleException.Program.Main(String[] args)
Help Link: http://www.CarsRUs.com

-> Custom Data:
-> TimeStamp: The car exploded at 9/12/2015 9:02:12 PM
-> Cause: You have a lead foot.

***** Out of exception logic *****

The Data property is useful in that it allows you to pack in custom information regarding the error at hand, without requiring the building of a new class type to extend the Exception base class. As helpful as the Data property may be, however, it is still common for .NET developers to build strongly typed exception classes, which handle custom data using strongly typed properties.

This approach allows the caller to catch a specific exception-derived type, rather than having to dig into a data collection to obtain additional details. To understand how to do this, you need to examine the distinction between system-level and application-level exceptions.

Image Source Code The SimpleException project is included in the Chapter 7 subdirectory.

System-Level Exceptions (System.SystemException)

The .NET base class libraries define many classes that ultimately derive from System.Exception. For example, the System namespace defines core exception objects such as ArgumentOutOfRangeException, IndexOutOfRangeException,StackOverflowException, and so forth. Other namespaces define exceptions that reflect the behavior of that namespace. For example, System.Drawing.Printing defines printing exceptions, System.IO defines input/output-based exceptions, System.Data defines database-centric exceptions, and so forth.

Exceptions that are thrown by the .NET platform are (appropriately) called system exceptions. These exceptions are generally regarded as nonrecoverable, fatal errors. System exceptions derive directly from a base class named System.SystemException, which in turn derives fromSystem.Exception (which derives from System.Object).

public class SystemException : Exception
{
// Various constructors.
}

Given that the System.SystemException type does not add any additional functionality beyond a set of custom constructors, you might wonder why SystemException exists in the first place. Simply put, when an exception type derives from System.SystemException, you are able to determine that the .NET runtime is the entity that has thrown the exception, rather than the code base of the executing application. You can verify this quite simply using the is keyword.

// True! NullReferenceException is-a SystemException.
NullReferenceException nullRefEx = new NullReferenceException();
Console.WriteLine("NullReferenceException is-a SystemException? : {0}",
nullRefEx is SystemException);

Application-Level Exceptions (System.ApplicationException)

Given that all .NET exceptions are class types, you are free to create your own application-specific exceptions. However, because the System.SystemException base class represents exceptions thrown from the CLR, you might naturally assume that you should derive your custom exceptions from the System.Exception type. You could do this, but you could instead derive from the System.ApplicationException class.

public class ApplicationException : Exception
{
// Various constructors.
}

Like SystemException, ApplicationException does not define any additional members beyond a set of constructors. Functionally, the only purpose of System.ApplicationException is to identify the source of the error. When you handle an exception deriving fromSystem.ApplicationException, you can assume the exception was raised by the code base of the executing application, rather than by the .NET base class libraries or .NET runtime engine.

Image Note In practice, few .NET developers build custom exceptions that extend ApplicationException. Rather, it is more common to simply subclass System.Exception; however, either approach is technically valid.

Building Custom Exceptions, Take 1

While you can always throw instances of System.Exception to signal a runtime error (as shown in the first example), it is sometimes advantageous to build a strongly typed exception that represents the unique details of your current problem. For example, assume you want to build acustom exception (named CarIsDeadException) to represent the error of speeding up a doomed automobile. The first step is to derive a new class from System.Exception/System.ApplicationException (by convention, all exception classes end with the Exceptionsuffix; in fact, this is a .NET best practice).

Image Note As a rule, all custom exception classes should be defined as public classes (recall, the default access modifier of a non-nested type is internal). The reason is that exceptions are often passed outside of assembly boundaries and should therefore be accessible to the calling code base.

Create a new Console Application project named CustomException, and copy the previous Car.cs and Radio.cs files into your new project using the Project Add Existing Item menu option (for clarity, be sure to change the namespace that defines the Car and Radio types fromSimpleException to CustomException). Next, add the following class definition:

// This custom exception describes the details of the car-is-dead condition.
// (Remember, you can also simply extend Exception.)
public class CarIsDeadException : ApplicationException
{}

As with any class, you are free to include any number of custom members that can be called within the catch block of the calling logic. You are also free to override any virtual members defined by your parent classes. For example, you could implement the CarIsDeadExceptionby overriding the virtual Message property.

As well, rather than populating a data dictionary (via the Data property) when throwing the exception, the constructor allows the sender to pass in a timestamp and reason for the error. Finally, the time stamp data and cause of the error can be obtained using strongly typed properties.

public class CarIsDeadException : ApplicationException
{
private string messageDetails = String.Empty;
public DateTime ErrorTimeStamp {get; set;}
public string CauseOfError {get; set;}

public CarIsDeadException(){}
public CarIsDeadException(string message,
string cause, DateTime time)
{
messageDetails = message;
CauseOfError = cause;
ErrorTimeStamp = time;
}

// Override the Exception.Message property.
public override string Message
{
get
{
return string.Format("Car Error Message: {0}", messageDetails);
}
}
}

Here, the CarIsDeadException class maintains a private field (messageDetails) that represents data regarding the current exception, which can be set using a custom constructor. Throwing this exception from the Accelerate() method is straightforward. Simply allocate, configure, and throw a CarIsDeadException type rather than a System.Exception (notice that in this case, you no longer need to fill the data collection manually).

// Throw the custom CarIsDeadException.
public void Accelerate(int delta)
{
...
CarIsDeadException ex =
new CarIsDeadException (string.Format("{0} has overheated!", PetName),
"You have a lead foot", DateTime.Now);
ex.HelpLink = "http://www.CarsRUs.com";
throw ex;
...
}

To catch this incoming exception, your catch scope can now be updated to catch a specific CarIsDeadException type (however, given that CarIsDeadException “is-a” System.Exception, it is still permissible to catch a System.Exception as well).

static void Main(string[] args)
{
Console.WriteLine("***** Fun with Custom Exceptions *****\n");
Car myCar = new Car("Rusty", 90);

try
{
// Trip exception.
myCar.Accelerate(50);
}
catch (CarIsDeadException e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.ErrorTimeStamp);
Console.WriteLine(e.CauseOfError);
}
Console.ReadLine();
}

So, now that you understand the basic process of building a custom exception, you might wonder when you are required to do so. Typically, you only need to create custom exceptions when the error is tightly bound to the class issuing the error (for example, a custom file-centric class that throws a number of file-related errors, a Car class that throws a number of car-related errors, a data access object that throws errors regarding a particular database table, and so forth). In doing so, you provide the caller with the ability to handle numerous exceptions on a descriptive error-by-error basis.

Building Custom Exceptions, Take 2

The current CarIsDeadException type has overridden the virtual System.Exception.Message property in order to configure a custom error message and has supplied two custom properties to account for additional bits of data. In reality, however, you are not required to override the virtual Message property, as you could simply pass the incoming message to the parent’s constructor as follows:

public class CarIsDeadException : ApplicationException
{
public DateTime ErrorTimeStamp { get; set; }
public string CauseOfError { get; set; }

public CarIsDeadException() { }

// Feed message to parent constructor.
public CarIsDeadException(string message, string cause, DateTime time)
:base(message)
{
CauseOfError = cause;
ErrorTimeStamp = time;
}
}

Notice that this time you have not defined a string variable to represent the message and have not overridden the Message property. Rather, you are simply passing the parameter to your base class constructor. With this design, a custom exception class is little more than a uniquely named class deriving from System.ApplicationException (with additional properties if appropriate), devoid of any base class overrides.

Don’t be surprised if most (if not all) of your custom exception classes follow this simple pattern. Many times, the role of a custom exception is not necessarily to provide additional functionality beyond what is inherited from the base classes but to supply a strongly named type that clearly identifies the nature of the error, so the client can provide different handler-logic for different types of exceptions.

Building Custom Exceptions, Take 3

If you want to build a truly prim-and-proper custom exception class, you would want to make sure your type adheres to .NET best practices. Specifically, this requires that your custom exception does the following:

· Derives from Exception/ApplicationException

· Is marked with the [System.Serializable] attribute

· Defines a default constructor

· Defines a constructor that sets the inherited Message property

· Defines a constructor to handle “inner exceptions”

· Defines a constructor to handle the serialization of your type

Now, based on your current background with .NET, you might have no experience regarding the role of attributes or object serialization, which is just fine. I’ll address these topics later (see Chapter 15 for information on attributes and Chapter 20 for details on serialization services). However, to complete your examination of building custom exceptions, here is the final iteration of CarIsDeadException, which accounts for each of these special constructors (the other custom properties and constructors would be as shown in the example in “Building Custom Exceptions, Take 2”):

[Serializable]
public class CarIsDeadException : ApplicationException
{
public CarIsDeadException() { }
public CarIsDeadException(string message) : base( message ) { }
public CarIsDeadException(string message,
System.Exception inner)
: base( message, inner ) { }
protected CarIsDeadException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base( info, context ) { }
// Any additional custom properties, constructors and data members...
}

Given that building custom exceptions that adhere to .NET best practices really differ by only their name, you will be happy to know that Visual Studio provides a code snippet template named Exception (see Figure 7-1) that will autogenerate a new exception class that adheres to .NET best practices. (Recall from Chapter 2 that a code snippet can be activated by typing its name, which is exception in this case, and pressing the Tab key twice.)

image

Figure 7-1. The Exception code snippet template

Image Source Code The CustomException project is included in the Chapter 7 subdirectory.

Processing Multiple Exceptions

In its simplest form, a try block has a single catch block. In reality, though, you often run into situations where the statements within a try block could trigger numerous possible exceptions. Create a new C# Console Application project named ProcessMultipleExceptions; add theCar.cs, Radio.cs, and CarIsDeadException.cs files from the previous CustomException example into the new project (via Project image Add Existing Item); and update your namespace names accordingly.

Now, update the Car’s Accelerate() method to also throw a predefined base class library ArgumentOutOfRangeException if you pass an invalid parameter (which you can assume is any value less than zero). Note the constructor of this exception class takes the name of the offending argument as the first string, followed by a message describing the error.

// Test for invalid argument before proceeding.
public void Accelerate(int delta)
{
if(delta < 0)
throw new
ArgumentOutOfRangeException("delta", "Speed must be greater than zero!");
...
}

The catch logic could now specifically respond to each type of exception.

static void Main(string[] args)
{
Console.WriteLine("***** Handling Multiple Exceptions *****\n");
Car myCar = new Car("Rusty", 90);
try
{
// Trip Arg out of range exception.
myCar.Accelerate(-10);
}
catch (CarIsDeadException e)
{
Console.WriteLine(e.Message);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}

When you are authoring multiple catch blocks, you must be aware that when an exception is thrown, it will be processed by the first appropriate catch. To illustrate exactly what the “first appropriate” catch means, assume you retrofitted the previous logic with an additional catchscope that attempts to handle all exceptions beyond CarIsDeadException and ArgumentOutOfRangeException by catching a general System.Exception as follows:

// This code will not compile!
static void Main(string[] args)
{
Console.WriteLine("***** Handling Multiple Exceptions *****\n");
Car myCar = new Car("Rusty", 90);

try
{
// Trigger an argument out of range exception.
myCar.Accelerate(-10);
}
catch(Exception e)
{
// Process all other exceptions?
Console.WriteLine(e.Message);
}
catch (CarIsDeadException e)
{
Console.WriteLine(e.Message);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}

This exception-handling logic generates compile-time errors. The problem is because the first catch block can handle anything derived from System.Exception (given the “is-a” relationship), including the CarIsDeadException and ArgumentOutOfRangeExceptiontypes. Therefore, the final two catch blocks are unreachable!

The rule of thumb to keep in mind is to make sure your catch blocks are structured such that the first catch is the most specific exception (i.e., the most derived type in an exception-type inheritance chain), leaving the final catch for the most general (i.e., the base class of a given exception inheritance chain, in this case System.Exception).

Thus, if you want to define a catch block that will handle any errors beyond CarIsDeadException and ArgumentOutOfRangeException, you could write the following:

// This code compiles just fine.
static void Main(string[] args)
{
Console.WriteLine("***** Handling Multiple Exceptions *****\n");
Car myCar = new Car("Rusty", 90);
try
{
// Trigger an argument out of range exception.
myCar.Accelerate(-10);
}
catch (CarIsDeadException e)
{
Console.WriteLine(e.Message);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine(e.Message);
}
// This will catch any other exception
// beyond CarIsDeadException or
// ArgumentOutOfRangeException.
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}

Image Note Where at all possible, always favor catching specific exception classes, rather than a general System.Exception. Though it might appear to make life simple in the short term (you may think, “Ah! This catches all the other things I don’t care about.”), in the long term you could end up with strange runtime crashes, as a more serious error was not directly dealt with in your code. Remember, a final catch block that deals with System.Exception tends to be very general indeed.

General catch Statements

C# also supports a “general” catch scope that does not explicitly receive the exception object thrown by a given member.

// A generic catch.
static void Main(string[] args)
{
Console.WriteLine("***** Handling Multiple Exceptions *****\n");
Car myCar = new Car("Rusty", 90);
try
{
myCar.Accelerate(90);
}
catch
{
Console.WriteLine("Something bad happened...");
}
Console.ReadLine();
}

Obviously, this is not the most informative way to handle exceptions since you have no way to obtain meaningful data about the error that occurred (such as the method name, call stack, or custom message). Nevertheless, C# does allow for such a construct, which can be helpful when you want to handle all errors in a general fashion.

Rethrowing Exceptions

When you catch an exception, it is permissible for the logic in a try block to rethrow the exception up the call stack to the previous caller. To do so, simply use the throw keyword within a catch block. This passes the exception up the chain of calling logic, which can be helpful if yourcatch block is only able to partially handle the error at hand.

// Passing the buck.
static void Main(string[] args)
{
...
try
{
// Speed up car logic...
}
catch(CarIsDeadException e)
{
// Do any partial processing of this error and pass the buck.
throw;
}
...
}

Be aware that in this example code, the ultimate receiver of CarIsDeadException is the CLR because it is the Main() method rethrowing the exception. Because of this, your end user is presented with a system-supplied error dialog box. Typically, you would only rethrow a partial handled exception to a caller that has the ability to handle the incoming exception more gracefully.

Notice as well that you are not explicitly rethrowing the CarIsDeadException object but rather making use of the throw keyword with no argument. You’re not creating a new exception object; you’re just rethrowing the original exception object (with all its original information). Doing so preserves the context of the original target.

Inner Exceptions

As you might suspect, it is entirely possible to trigger an exception at the time you are handling another exception. For example, assume you are handling a CarIsDeadException within a particular catch scope and during the process you attempt to record the stack trace to a file on your C: drive named carErrors.txt (you must specify you are using the System.IO namespace to gain access to these I/O- centric types).

catch(CarIsDeadException e)
{
// Attempt to open a file named carErrors.txt on the C drive.
FileStream fs = File.Open(@"C:\carErrors.txt", FileMode.Open);
...
}

Now, if the specified file is not located on your C: drive, the call to File.Open() results in a FileNotFoundException! Later in this book, you will learn all about the System.IO namespace where you’ll discover how to programmatically determine whether a file exists on the hard drive before attempting to open the file in the first place (thereby avoiding the exception altogether). However, to stay focused on the topic of exceptions, assume the exception has been raised.

When you encounter an exception while processing another exception, best practice states that you should record the new exception object as an “inner exception” within a new object of the same type as the initial exception. (That was a mouthful!) The reason you need to allocate a new object of the exception being handled is that the only way to document an inner exception is via a constructor parameter. Consider the following code:

catch (CarIsDeadException e)
{
try
{
FileStream fs = File.Open(@"C:\carErrors.txt", FileMode.Open);
...
}
catch (Exception e2)
{
// Throw an exception that records the new exception,
// as well as the message of the first exception.
throw new CarIsDeadException(e.Message, e2);
}
}

Notice, in this case, I have passed in the FileNotFoundException object as the second parameter to the CarIsDeadException constructor. After you have configured this new object, you throw it up the call stack to the next caller, which in this case would be the Main()method.

Given that there is no “next caller” after Main() to catch the exception, you would be again presented with an error dialog box. Much like the act of rethrowing an exception, recording inner exceptions is usually useful only when the caller has the ability to gracefully catch the exception in the first place. If this is the case, the caller’s catch logic can use the InnerException property to extract the details of the inner exception object.

The finally Block

A try/catch scope may also define an optional finally block. The purpose of a finally block is to ensure that a set of code statements will always execute, exception (of any type) or not. To illustrate, assume you want to always power down the car’s radio before exiting Main(), regardless of any handled exception.

static void Main(string[] args)
{
Console.WriteLine("***** Handling Multiple Exceptions *****\n");
Car myCar = new Car("Rusty", 90);
myCar.CrankTunes(true);
try
{
// Speed up car logic.
}
catch(CarIsDeadException e)
{
// Process CarIsDeadException.
}
catch(ArgumentOutOfRangeException e)
{
// Process ArgumentOutOfRangeException.
}
catch(Exception e)
{
// Process any other Exception.
}
finally
{
// This will always occur. Exception or not.
myCar.CrankTunes(false);
}
Console.ReadLine();
}

If you did not include a finally block, the radio would not be turned off if an exception were encountered (which might or might not be problematic). In a more real-world scenario, when you need to dispose of objects, close a file, or detach from a database (or whatever), a finallyblock ensures a location for proper cleanup.

Exception Filters

The current release of C# introduces a new (and completely optional) clause that can be placed on a catch scope, via the when keyword. When you add this clause, you have the ability to ensure that the statements within a catch block are executed only if some condition in your code holds true. This expression must evaluate to a Boolean (true or false) and can be obtained by using a simple code statement in the when definition itself or by calling an additional method in your code. In a nutshell, this approach allows you to add “filters” to your exception logic.

First, assume you have added a few custom properties to your CarIsDeadException.

public class CarIsDeadException : ApplicationException
{
...
// Custom members for our exception.
public DateTime ErrorTimeStamp { get; set; }
public string CauseOfError { get; set; }

public CarIsDeadException(string message,
string cause, DateTime time)
: base(message)
{
CauseOfError = cause;
ErrorTimeStamp = time;
}
}

Also assume the Accelerate() method uses this new constructor when throwing the error.

CarIsDeadException ex =
new CarIsDeadException(string.Format("{0} has overheated!", PetName),
"You have a lead foot", DateTime.Now);

Now, consider the following modified exception logic. Here, I have added a when clause to the CarIsDeadException handler to ensure the catch block is never executed on a Friday (a contrived example, but who wants their automobile to break down on the weekend?). Notice that the single Boolean statement in the when clause must be wrapped in parentheses (also note you are now printing out a new message in this scope, which will output only when the when condition is true).

catch (CarIsDeadException e) when (e.ErrorTimeStamp.DayOfWeek != DayOfWeek.Friday)
{
// This new line will only print if the when clause evaluates to true.
Console.WriteLine("Catching car is dead!");

Console.WriteLine(e.Message);
}

While the chances are you will simply have a catch clause for a given error under any condition, as you can see, the new when keyword allows you to get much more granular when responding to runtime errors.

Debugging Unhandled Exceptions Using Visual Studio

Do be aware that Visual Studio supplies a number of tools that help you debug unhandled custom exceptions. Again, assume you have increased the speed of a Car object beyond the maximum but this time did not bother to wrap your call within a try block.

Car myCar = new Car("Rusty", 90);
myCar.Accelerate(2000);

If you start a debugging session within Visual Studio (using the Debug image Start Debugging menu selection), Visual Studio automatically breaks at the time the uncaught exception is thrown. Better yet, you are presented with a window (see Figure 7-2) displaying the value of the Messageproperty.

image

Figure 7-2. Debugging unhandled custom exceptions with Visual Studio

Image Note If you fail to handle an exception thrown by a method in the .NET base class libraries, the Visual Studio debugger breaks at the statement that called the offending method.

If you click the View Detail link, you will find the details regarding the state of the object (see Figure 7-3).

image

Figure 7-3. Viewing exception details

Image Source Code The ProcessMultipleExceptions project is included in the Chapter 7 subdirectory.

Summary

In this chapter, you examined the role of structured exception handling. When a method needs to send an error object to the caller, it will allocate, configure, and throw a specific System.Exception-derived type via the C# throw keyword. The caller is able to handle any possible incoming exceptions using the C# catch keyword and an optional finally scope. As shown, C# 6.0 now supports the ability to create exception filters using the optional when keyword.

When you are creating your own custom exceptions, you ultimately create a class type deriving from System.ApplicationException, which denotes an exception thrown from the currently executing application. In contrast, error objects deriving fromSystem.SystemException represent critical (and fatal) errors thrown by the CLR. Last but not least, this chapter illustrated various tools within Visual Studio that can be used to create custom exceptions (according to .NET best practices) as well as debug exceptions.