Testing

webTiger Logo Wide

.NET Tutorials – 6 – Error Handling

Microsoft .NET Logo

What are exceptions? In .NET terms, exceptions provide a mechanism for enabling the raising/reporting of errors that may occur during the execution of the software. This tutorial covers exceptions and exception handling in detail.

In this article:

Introduction

In real terms, we can categorise problems with computer software into three main types:

  • Bug. These are genuine coding errors, typically on the part of the programmer. An application that opens a file, reads it but forgets to close it (and therefore holds the file in a locked state for the entire lifecycle of the application) is an example of a software bug.
  • User error. These are typically caused by the user working with your application. They are normally due to issues like running activities in an incorrect order, inputting data in an invalid format, etc. Software developers should always attempt to cater for potential user error scenarios (e.g. enforcement of activity flow-control, validate inputted data before processing it, etc.)
  • Exception. These are runtime anomalies that are difficult to account for while programming an application. Examples might include attempting to open a corrupted or missing file, attempting to write information to the Windows Registry without sufficient privileges, etc. In these cases the software developer may have little control over these unforeseen (‘exceptional’) circumstances.

Historically, return codes (numeric values) were used to represent software errors. This had the obvious drawback that programmers had to understand what the error code meant in order to be able to process that error in a meaningful way.

Microsoft published a raft of error codes with its Windows operating system and software programmers sometimes ended up writing complex conditional statements depending on the number of error codes that could be returned.

Also, error codes were usually passed back to the caller using the ‘return type’ of a function, meaning some functions (those that could not support a return type, such as class constructors) could not provide meaningful error information of any kind.

With the advent of object orientated programming (OOP) concepts, many developers improved their own custom error handling techniques by defining interfaces and custom classes that allowed for richer error descriptions. This still generally involved the use of the return type of a function or method to pass the error information back to the calling process though.

The .NET Framework introduced a structured exception handling philosophy.

.NET Structured Exception Handling

Microsoft’s solution to these historical error handling problems was to introduce ‘Structured Exception Handling’ (SEH) in .NET. SEH imposes a set of standards on how error handling should be approached.

A word of warning though: Visual Basic (VB.NET) has also retained its legacy error handling mechanism from its predecessor (VB6) due to pressure from the wider software development industry. This is being mentioned so that you are aware that an alternative error handling mechanism exists in VB.NET. Use of the older error handling mechanism is not recommended, and the VB6 compatibility was only really left in place to help ease the burden of source code migration from VB6 to VB.NET.

SEH relies on the following language capabilities (which all Common Language Specification (CLS) compliant .NET languages support):

  • A class that represents the details of a given type of exception that can occur.
  • Keywords enabling the implementation of SEH techniques in your code. In C#, those keywords are: try, catch, throw and finally. The actual keywords and specific implementation may differ between the programming languages (e.g. VB.NET uses capitalised versions such as Try, Catch, etc.) – it’s the structure that remains common.
  • A ‘guarded’ code-block (i.e. encapsulation of a block of code within which the error handling applies).
  • One or more error filtering code-blocks that can handle software exceptions (these are ‘exception handler’ blocks).

The .NET Runtime includes a built-in exception handling mechanism. In the absence of localised (programmed) error handling the runtime will terminate an application, displaying information about the software exception that occurred. Wherever possible, it is recommended that software developers cater for exceptions within their code and not rely on the ‘fall-back’ mechanism.

The .NET Framework defines a wealth of exception types that may be used and consumed as required. It also provides a means to create custom exception types, and where this is done Microsoft recommends those exceptions be derived from the System.Exception base class.

Implementation of .NET structured error handling is done as follows:

  • The guarded code block is encapsulated within a try statement.
  • One or more localised error handling code-blocks are implemented within catch statements.
  • Common functionality that should be executed in all cases (both on success and in the event of an error) can be encapsulated within a finally statement. This statement is normally used for cleanup activities.
  • Where the error occurs the process throws an exception of the appropriate type.

Let’s look at implementing exception handling in our programs. Consider the following:

// Console application, Program.cs

namespace ErrorHandling
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                int myNumber = Program.PromptForNumber();                
                Console.WriteLine("You entered: {0}", myNumber);
            }
            catch (FormatException formatError)
            {
                Console.WriteLine("DATA FORMAT ERROR: {0}", formatError.Message);
            }
            catch (ArgumentException argumentError)
            {
                Console.WriteLine("VALUE RANGE ERROR: {0}", argumentError.Message);
            }
            catch (Exception generalError)
            {
                // (Catch all - traps any other errors)
                Console.WriteLine("GENERIC ERROR: {0}", generalError.Message);
            }
            finally
            {
                Console.WriteLine();
                Console.Write("Press Enter to QUIT");
                Console.ReadLine();
            }
        }

        private static int PromptForNumber()
        {
            Console.WriteLine("Please enter a value between 1 and 10");
            Console.WriteLine("(Or type ERROR to raise a general error)");
            Console.WriteLine();
            
            string valueEntered = Console.ReadLine();
            
            if (!string.IsNullOrEmpty(valueEntered) && 
                string.Compare(valueEntered.Trim(), "ERROR", true) == 0)
            {
                throw new Exception("You wanted a general error - here it is!");
            }

            int numericValue = int.MinValue;

            if (string.IsNullOrEmpty(valueEntered) || 
                !int.TryParse(valueEntered, out numericValue))
            {
                throw new FormatException();
            }
            else if (numericValue < 1 || numericValue > 10)
            {
                throw new ArgumentException();
            }
            else
            {
                return numericValue;
            }
        }
    }
}Code language: C# (cs)

The above code creates a guard block (try) around the call to the PromptForNumber method and outputting the outcome to the console. If the call to PromptForNumber or the Console.WriteLine call immediately afterwards fail and result in an exception being thrown then the catch blocks will attempt to filter and process any software exceptions detected.

The catch blocks in the example are ordered. The first matching exception object specification will trigger the body of that block to execute.

For example, if you moved the catch (Exception generalError) code block to the top of the catch statements then none of the others would ever execute. This is because ‘Exception is the most generalised software exception class there is and all others should inherit from it. So, you’re most generalised exception type should always be your last catch block.

Error Handling is NOT for Flow Control

It is very tempting to start using exception handling for software flow control, but this should be avoided. The throwing and catching of exceptions can be a time consuming and uncertain process, and using it to control the execution flow of your application will therefore reduce your application’s efficiency considerably.

To demonstrate the amount of time involved in handling errors, let’s look at a simple example where exception handling is used to determine whether a file exists before loading the file for processing. In this simple example, we will concentrate on the opening activity only.

namespace ErrorsAsFlowControlAnalysis
{
    internal class Program 
    {
        static void Main(string[] args)
        {
            const string filename = @"C:\Temp\aFile_ThatIsNotThere.txt";

            TimeSpan executionTime = TimeSpan.Zero;
            DateTime timeStamp = DateTime.Now;
                
            Console.WriteLine("Opening file {0} (using error flow)", filename);
                       
            try
            {
                timeStamp = DateTime.Now;
                File.OpenText(filename);
            }
            catch (FileNotFoundException)
            {
                executionTime = DateTime.Now - timeStamp;
                Console.WriteLine(
                    "File not found (using error flow), execution time {0}ms", 
                    executionTime.Milliseconds);
            }

            Console.WriteLine();
            Console.WriteLine("Opening file {0} (checking with File.Exists)", filename);
                
            timeStamp = DateTime.Now;
            
            if (!File.Exists(filename))
            {
                executionTime = DateTime.Now - timeStamp;
                Console.WriteLine(
                    "File not found (checked with File.Exists), execution time {0}ms", 
                    executionTime.Milliseconds);
            }
            else
            {
                //open the file, etc.
                Console.WriteLine("File exists - this is not expected!");
            }

            Console.ReadLine();
        }
    }
}Code language: C# (cs)

if you create a Console Application code project and replace the Program class with the one above, and run it, then you might get an output something like this:

Opening file C:\Temp\aFile_ThatIsNotThere.txt (using error flow)
File not found (using error flow), execution time 16ms

Opening file C:\Temp\aFile_ThatIsNotThere.txt (checking with File.Exists)
File not found (checked with File.Exists), execution time 0msCode language: plaintext (plaintext)

As you can see, using exception handling to check if a file exists is very costly in terms of performance compared to taking the time to find and use the correct mechanism for the job.

Although this is a trivial case, consider what impact if might have on real-time or high-volume systems where performance bottlenecks could prove very costly or affect system behaviour.

Beyond System Exceptions

Even though the .NET Framework provides a wealth of built-in exception types, there will come a time when you won’t be able to find one that is suitable for your specific needs. Enter the custom exception type!

The .NET Framework allows software developers to define their own exception types based on the requirements they may have in a particular programming scenario. Microsoft recommends that all custom exception types are derived from System.Exception.

Aside: Microsoft did recommend that application related exceptions should derived from System.ApplicationException but both Microsoft and the software industry at large eventually agreed there was no benefit to be gained from deriving custom type from ApplicationException. The initial problem was that Microsoft’s documentation was inconsistent, but buried in the Remarks section of the ApplicationException topic of the MSDN documentation was the following passage:

If you are designing an application that needs to create its own exceptions, you are advised to derive custom exceptions from the [System.Exception] class. It was originally thought that custom exceptions should derive from the ApplicationException class; however in practice this has not been found to add significant value.

Custom exception types are classes in their own right and that means a developer is reasonably free to decide how to represent the error. Inclusion of methods that expose functionality is not recommended, however, as an exception should represent data and state – not functionality.

Let’s work through an example to demonstrate the power of custom exceptions.

Consider the following:

using System;

namespace CustomErrorTypes
{
    public class InvalidAccountNumberException : Exception
    {
        private const string CustomMessage = "The supplied account number is invalid. “ +
            “Account numbers should be of the form: 1234-567890";

        public InvalidAccountNumberException()
            : base(InvalidAccountNumberException.CustomMessage)
        {
        }

        public InvalidAccountNumberException(string message)
            : base(message)
        {
        }

        public InvalidAccountNumberException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        public InvalidAccountNumberException(
            System.Runtime.Serialization.SerializationInfo info, 
            System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
        }
    }
}Code language: C# (cs)

The above custom exception type could be used in conjunction with a bank account searching service, for example. The user enters an account number and hits a search button. The search routine identifies the account number format as being invalid, and throws an instance of the custom exception type.

Why is this relevant or necessary? Well, it isn’t strictly speaking necessary – a generic System.Exception could be thrown with exactly the same message as the default one. But it is relevant and important because of the benefits it offers.

Having an exception type that has a readable and descriptive name greatly improves code readability. Throwing a System.Exception just tells the reader that an exception is being caught, but throwing a CustomErrorTypes.InvalidAccountNumberException tells the reader that the exception was caused by an invalid account number being supplied to the source of the exception.

Custom exception types also allow certain software exceptions to be handled differently ways to any other exceptions. If doesn’t just have to be custom exception types though, as Microsoft have a plethora of exception types in the FCL and those may fit the requirements without needing a new class to do it. For example, System.FormatException can be used where the format of supplied arguments or data is not as expected.

The above custom exception code example could be expanded into a set of custom types,such as:

CustomErrorTypes.AccountNumberFormatException
CustomErrorTypes.InvalidAccountNumberException
CustomErrorTypes.AccountClosedExceptionCode language: plaintext (plaintext)

This adds value and context to the software exception being caught and processed, so that a developer can take the most appropriate action without needing to evaluate individual error messages or have to display a generic error message to the user (or, even worse, the raw message catch).

It is worth noting that an exception type I would not define in this scenario is:

CustomErrorTypes.AccountNumberNotFoundExceptionCode language: plaintext (plaintext)

Although that might seem like a sensible option at first, the performance impact of throwing that as an exception instead of returning a null or empty collection is significant; and we’re almost back to using error handling for flow-control again.

A custom exception type I regularly end up defining and using is a UserFriendlyException. As the name suggests, it identifies itself as a user-friendly exception whose message can be blindly displayed to a user without the risk of it containing confusing technical error detail or program data. If your UI code is able to detect (catch) a UserFriendlyException instance, then it can handle it differently to other exception types where it may need to display a more generic “An error has occurred” style message to the user.

Exception Event Handlers

One of the key tenets of a well behaved application is robust and comprehensive exception handling. Software programmers should always try to develop their code to handle all likely error scenarios.

When developing a console application your options are limited to an in-line try-catch block and supplementary implementation of a handler for the UnhandledException event. When undertaking Windows Forms and Web Forms development, additional options are open to you (but these will be discussed in the appropriate tutorials later on.)

Before we can discuss the UnhandledException event we have to introduce a new topic, and that is Application Domains.

Historically, applications have been loaded by operating systems as separate processes and it has been these process boundaries that isolate applications from each other. Microsoft introduced the concept of application domains in .NET to offer a more versatile and secure solution than that provided by process boundaries alone.

Several application domains can operate within a single process with the same degree of isolation that is offered by running applications within separate processes but without the overhead of cross-process calls and process switching.

As well as execution efficiency, application domains offer better application-level security. For example, several web applications can run in complete isolation within a single web browser process but those applications are not able to access each other’s data and resources.

Prior to .NET and the introduction of application domains, the data and resources exposed by a process would be globally visible within that process space (in our web browser example this would have meant multiple web browser processes, each hosting a single web application to ensure data and resource isolation).

Getting back to structured error handling, the UnhandledException event is exposed through application domain infrastructure via the System.AppDomain.CurrentDomain class and provides a last-chance mechanism for cleaning up an application before it is terminated due to an unhandled exception. The default underlying system functionality will be executed regardless of whether or not your application registers a handler for this event – the event is exposed simply to enable the running of cleanup routines before the application is terminated by the runtime.

The isolated behaviour associated with the UnhandledException event is by design – Microsoft quite rightly decided that it was easier to debug application crashes due to unhandled exceptions than to have to debug situations when the application stops responding because of them. (It is probably worth noting that this behaviour was only properly implemented from .NET v2.0 onwards and that in earlier versions of the Framework the behaviour may differ.) This is in stark contrast to most other events that you can register handlers with (such as the Application.ThreadException event that will be discussed in the Windows Forms tutorial) where default functionality is omitted.

Let’s look at a worked example to demonstrate all this. (Also, notice in the switch statement that throwing an exception means you don’t have to use the break jump statement.)

using System;

namespace StructuredErrorHandling
{
    internal class Program
    {
        static void Main(string[] args)
        {
            bool triggerError = false;
            string data = string.Empty;

            try
            {
                Console.WriteLine("Select one of the following options:");
                Console.WriteLine("1) application throws an argument exception within " + 
                    "the try-catch block.");
                Console.WriteLine("2) application throws a format exception within " + 
                    "the try-catch block.");
                Console.WriteLine("3) application throws a generic exception within " + 
                    "the try-catch block.");
                Console.WriteLine("Otherwise: application throws a generic exception " + 
                    "outside the try-catch block.");

                data = Console.ReadLine().Trim();

                switch (data)
                {
                    case "1": 
                        throw new ArgumentException("some argument is incorrect");
                    case "2": 
                        throw new FormatException("the format of the data is incorrect");
                    case "3": 
                        throw new Exception("something went wrong!");
                    default: 
                        triggerError = true;
                        break;
                }
            }
            catch (ArgumentException argumentError)
            {
                Program.ShowInformation(string.Format(
                    "An argument exception was handled, error message: {0}", 
                    argumentError.Message));
            }
            catch (FormatException formatError)
            {
                Program.ShowInformation(string.Format(
                    "A format exception was handled, error message: {0}", 
                    formatError.Message));
            }
            catch (Exception error)
            {
                Program.ShowInformation(string.Format(
                    "A generic exception was handled, error message: {0}", 
                    error.Message));
            }

            if (triggerError)
            {
                throw new Exception(
                    "Something went wrong outside our fluffy protective code block!");
            }
        }

        static void ShowInformation(string information)
        {
            Console.WriteLine(information);
            Console.WriteLine("(Press any key to continue)");
            Console.Read();
        }

        static void CurrentDomain_UnhandledException(object sender, 
            UnhandledExceptionEventArgs e)
        {
            Console.WriteLine();
            Console.WriteLine("UNHANDLED ERROR...");
            
            if (e.ExceptionObject is Exception)
            {
                Program.ShowInformation(string.Format("Error message: {0}", 
                    ((Exception)e.ExceptionObject).Message));
            }
            else
            {
                Program.ShowInformation(
                    "The exception isn't of a recognised type, no error information is available");
            }
        }
    }
}Code language: C# (cs)

Build the console app and run it three times. Enter 1, 2, 3 in turn, once for each app run you do. Notice that the expected error types are caught and processed, displaying the appropriate error message each time.

Now run the app again, and this time just hit enter. Notice that the raw error message thrown from outside the try-catch block is not handled by the code and instead falls back on the runtime to capture the unhandled exception and terminate the program.

At this point you might be confused because we’ve defined a method to cater for unhandled exceptions. Well, yes, we have but it isn’t wired up yet. Add the following to the top of the Main() method to wire up the event handler:

AppDomain.CurrentDomain.UnhandledException += 
    new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);Code language: C# (cs)

If you build and run the application again, hitting enter without typing anything should now display the expected error handling output. The program still terminates but at least you have an opportunity to cleanup the app before it does.

Something else to note is that UnhandledExceptionEventArgs is passed to the event handler. This object includes an ExceptionObject property that may or may not be populated with an exception instance, and an IsTerminating property that indicates if the runtime has failed to recover from the error and is now terminating the program.

It is worth checking the IsTerminating property as some unhandled exceptions may not result in program termination.