Exception Handling

.Exception Handling
Exception Handling | Throwing Exceptions | Exceptions Across Threads | Creating Custom Exceptions

"The .NET framework uses exceptions to signal errors. An exception is an object that contains data about the error which occurred. If an exception goes unhandled, it will cause the process to terminate."

Exceptions represent errors that occur during the execution of an application. In .NET there are two types of exceptions:

  1. Exceptions Generated by An Executing Program

  2. Exceptions Generated by the Common Language Runtime

All exceptions, such as SystemException, are derived from the Exception base class. The runtime throws exceptions derived from SystemException when it encounters an error. Some these of exceptions derived from SystemException represent recoverable conditions, such as ArgumentException, while others represent nonrecoverable conditions, such as StackOverflowException and OutofMemoryException.

.NET uses an exception handling model based on exception objects and protected blocks of code in a try/catch/finally structure. Exceptions propagate up the call stack until a catch statement is found for the exception. If an exception goes unhandled, it is caught by the default handler which displays the exception name, message, and stack trace before terminating the process. In addition to the exceptions created by the runtime, applications can also create their own custom exceptions by deriving from the Exception base class. Applications can also be coded to throw or rethrow exceptions.

Well-designed applications handle exceptions to keep the application from crashing or performing incorrectly. However the process of throwing and handling exceptions consumes a significant amount of systems resources, so they should only be performed for truly extraordinary conditions.
MSDN published the article Best Practices for Exceptions which contains recommendations for handling exceptions. A few of these recommendations are:

  1. When exceptions are the result of faulty code, the code should be fixed instead of handling the exception. Debug.Assert and Trace.Assert can be used to help identify programming errors, such as unexpected null values.

  2. Do not catch an exception unless you can handle it and leave the application in a known state. If you catch System.Exception, rethrow it using the throw keyword at the end of the catch block.

  3. Design classes so that an exception is never thrown in normal use. That is, do NOT use exceptions for something as predictable as invalid input data.

  4. In most cases, use the predefined exception types. Introduce a new exception class to enable a programmer to take a different action in code based on the exception class.

Additionally system failures do not normally use exception handling. Instead, you may be able to use an event such as AppDomain.UnhandledException and call the Environment.FailFast method to log exception information and notify the user of the failure before the application terminates. That is, do NOT use exceptions for something as predictable as invalid input data.

The Exception class is the base class for all exceptions. This provides a consistent way to handle errors within the .NET framework. Exceptions can be raised by your application or by code you call from a shared library (either managed code or unmanaged code). .NET allows managed and unmanaged code to work together to handle exceptions. Custom exceptions can also be derived from the Exception base class. Microsoft provides guidelines for Designing Custom Exceptions.

The Exception class contains properties that can help identify and locate the error. Three of the most useful properties are:

  1. Message - provides details about the cause of an exception.

  2. StackTrace - contains a stack trace that can be used to determine where an error occurred.

  3. InnerException - used to create and preserve a series of exceptions during exception handling. You can use this property to create a new exception that contains previously caught exceptions.
.Exception Properties
Exception Properties

using System;

namespace ExceptionsExample1
{
    class Program
    {
        static void Main()
        {
            string s = "Kevin";

            try
            {
                ParseInteger(s);                  
            }
            catch (Exception e)
            {
                Console.WriteLine("---------------------------------------------------------");
                Console.WriteLine("----------------- Exception Properties ------------------");
                Console.WriteLine("---------------------------------------------------------\n");
                Console.WriteLine("Message -> {0}\n", e.Message);
                Console.WriteLine("HelpLink -> {0}\n", e.HelpLink);
                Console.WriteLine("StackTrace -> {0}\n", e.StackTrace);
                Console.WriteLine("Source -> {0}\n", e.Source);
                Console.WriteLine("TargetSite -> {0}\n", e.TargetSite);
                Console.WriteLine("InnerException -> {0}\n", e.InnerException);
                Console.WriteLine("Data -> {0}\n", e.Data);
            }
        }

        private static int ParseInteger(string s)
        {
            int i = int.Parse(s);
            return i;
        }
    }
}

Top




Exception Handling

To implement exception handling the code which could potentially throw an exception is surrounded with a try statement. The try statement marks a guarded code block which is subject to error handling. The try statement must be followed by either, or both:

  1. one or more catch blocks - executes when error occurs within the try block. Catch blocks should be specified from most-specific error to least pecific error. The first matching catch block will be executed, i.e. only one catch block executes for a given exception. The base class Exception will catch all exceptions, however this is generally discouraged because it is too generic. Instead specific exceptions should be used in the catch block(s). Also, prior versions of .NET had situations where no exception was passed to the catch block. This is no longer the case. It is advised that an exception always be passed to the catch block, otherwise the catch logic will not have access to the exception object.

  2. one finally block - always executes even when no error occurs within the try block (Note: the finally block will not execute if the Environment.FailFast method is called.) The finally block executes after the catch block(s) if any are present. The finally block is useful for performing cleanup after an error, such as closing network connections. Errors within the finally block should be avoided, as it will cause the execution to more outside the block and the original exception will be lost.

.NET has many exceptions defined. Some are listed at SystemException Class. A list of exceptions is also available in Visual Studio by pressing Ctrl+Alt+E. Some of the commonly used exceptions are:

  1. ArgumentException - one of the arguments provided to a method is not valid.
  2. FormatException - the format of an argument does not meet the parameter specifications of the invoked method.
  3. InvalidCastException - thrown for invalid casting or explicit conversion.
  4. IOException - thrown when an I/O error occurs.
  5. InvalidOperationException - method call is invalid for the object's current state.
  6. NotSupportedException - an invoked method is not supported, or when there is an attempt to read, seek, or write to a stream that does not support the invoked functionality.
  7. NotImplementedException - requested method or operation is not implemented.
  8. NullReferenceException - when there is an attempt to dereference a null object reference.
Using Statement

.Net encapsulates some unmanaged resources (e.g. file handles, database connections, etc.) with a class that implements IDisposable which defines a Dispose() method to clean up the resource. On these encapsulated resources, the Using statement provides a syntax for calling the Dispose() method which is equivalent to calling the Dispose() method in a final block. That is, the following code:

           using (StreamReader myReader = File.OpenText("kfile.txt"))
            {

            }

is equivalent to this code.

            StreamReader myReader = File.OpenText("kfile.txt");
            try
            {

            }
            finally
            {
                if (myReader != null)
                    ((IDisposable)myReader).Dispose();
            }

However, the Using statement does not allow for the checking of exceptions. So if exceptions need to be checked, instead of the Using statement, the Dispose method can be coded in the final block as shown below.

            StreamReader myReader = null;
            string fileName = "kfile.txt";
            string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            string fullName = System.IO.Path.Combine(desktopPath, fileName);

            try
            {
                myReader = File.OpenText(fullName);
                if (myReader.EndOfStream) return;
                Console.WriteLine(myReader.ReadToEnd());
            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine("File: {0} was not found.", fullName);
            }
            catch (Exception e)
            {
                Console.WriteLine("Error: {0}", e.Message);
            }
            finally
            {
               if (myReader != null)
                   ((IDisposable)myReader).Dispose();
            }

Top




Throwing Exceptions

.Throw vs Throw e
'throw ex' loses stack trace (top) where 'throw' keeps stack trace (bottom)

"throw vs throw(ex) - to keep the original stack trace, use throw without specifying the exception."

.NET allows applications to throw and rethrow exceptions. Usually the exception thrown is one which is already defined in the .NET framework, however there are situations where a custom exceptions may be more suitable. Exceptions can be caught in one thread and thrown to another thread. Sometimes a exception can be caught, some action is perform, then the same, or a more specific exception is thrown.

In order to throw an exception you must first create a new instance of an exception. It is recommended that exceptions should not be reused, so each time you throw an exception, you should create a new instance. Then the keyword throw is used to throw the exceptions. This causes the runtime to begin looking for catch and finally blocks. In most scenarios, instances of exceptions derived from theSystemException should not be thrown.

The following program contains a method which checks for a missing argument and throws anArgumentNullException exception if the argument is null or white spaces. The thrown exception is caught by the calling code.

Method which Throws ArgumentNullException

using System;
using System.IO;

public class Program
{
    private static string OpenAndRead(string fullName)
    {
        if (string.IsNullOrWhiteSpace(fullName))
           throw new ArgumentNullException("fullName", "FullName is required.");

        return File.ReadAllText(fullName); ;
    }

    public static void Main()
    {     
        string fullName = "";
        string contents = null;

        try
        {
            contents = OpenAndRead(fullName);
        }
        catch (Exception e)
        {
            Console.WriteLine("-- Main catch --");
            Console.WriteLine("Message     -> {0}\n", e.Message);
            Console.WriteLine("Stack Trace -> {0}", e.StackTrace);
        }

        Console.WriteLine(contents);
    }
}

When an exception is caught, the same, or another exception can be thrown. This can allow actions to be performed, such as writing to a log file, and then rethrowing the same exception. It can also to provide more debugging information, such a adding information to the exception or throwing a more specific exception for the error. The Exception.InnerException property can be used to create and preserve a series of exceptions.

Some guidelines for throwing exceptions include:

  1. Do not throw exceptions which are derived from the SystemException class.
  2. Use the throw statement without any arguments in order to preserve the original stack trace.
  3. Return null for extremely common error cases instead of throwing an exception. An extremely common error case can be considered normal flow of control and by returning null in these cases; you minimize the performance impact to an app.
  4. Design classes so that an exception is never thrown in normal use. For example, a FileStream class provides methods that help determine whether the end of the file has been reached. This avoids the exception that is thrown if you read past the end of the file.
  5. In most cases, use the predefined exception types. Introduce a new exception class to enable a programmer to take a different action in code based on the exception class.
  6. Throw an InvalidOperationException exception if a property set or method call is not appropriate given the object's current state.
  7. For most apps, derive custom exceptions from the Exception class. Deriving from the ApplicationException class doesn't add significant value.
  8. End exception class names with the word "Exception".
  9. Clean up intermediate results when throwing an exception. Callers should be able to assume that there are no side effects when an exception is thrown from a method.
  10. Do report execution failures by throwing exceptions. If a member cannot successfully do what it is designed to do, that should be considered an execution failure and an exception should be thrown.
  11. Consider terminating the process by calling System.Environment.FailFast(System.String) (a .NET Framework version 2.0 feature) instead of throwing an exception, if your code encounters a situation where it is unsafe to continue executing.
  12. Do not use exceptions for normal flow of control, if possible. Except for system failures and operations with potential race conditions, framework designers should design APIs so that users can write code that does not throw exceptions. For example, you can provide a way to check preconditions before calling a member so that users can write code that does not throw exceptions.
  13. Do not have public members that can either throw or not throw exceptions based on some option.
  14. Do not have public members that return exceptions as the return value or as an out parameter.
  15. Avoid explicitly throwing exceptions from finally blocks.

The following program contains a method which catches a file I/O exception and throws it back to the calling code. The code could throw a different exception in place of the original, but in this case it is just throwing the original exception again. Note that the throw statement does not include the exceptions as an argument. Without the exception argument on the throw statement, the original stack trace is preserved.

Method which Throws ArgumentNullException

using System;
using System.IO;

public class Program
{
    private static string OpenAndRead(string fullName)
    {
        string fileContents = null;
        try
        {
            fileContents = File.ReadAllText(fullName);
        }
        catch (Exception e)
        {
            throw;
        }

        return fileContents;
    }

    public static void Main()
    {
        string fileName = "kfile2.txt";
        string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
        string fullName = System.IO.Path.Combine(desktopPath, fileName);

        string contents = null;
        try
        {
            contents = OpenAndRead(fullName);
        }
        catch (Exception e)
        {
            Console.WriteLine("-- Main catch --");
            Console.WriteLine("Message     -> {0}\n", e.Message);
            Console.WriteLine("Stack Trace -> {0}", e.StackTrace);
        }

        Console.WriteLine(contents);
    }
}

Top




Exceptions Across Threads

.ExceptionDispatchInfo Class
Exception Thrown with ExceptionDispatchInfo Class

"The ExceptionDispatchInfo class allows you to share exception data between threads. The Throw() method of this class can even be used outside of the catch block to throw exceptions across threads."

Introduced in C# 5.0 is the ExceptionDispatchInfo class which represents an exception whose state is captured at a certain point in the code. The Capture() method of this class will store the exception details and then use the Throw() method to send throw the exception to another thread. This feature is used by the .NET framework for the async/await process to capture the exception on the async thread and rethrow it on the executing thread.

The following program used the ExceptionDispatchInfo class to capture rethrow an exception:

Exception Thrown with ExceptionDispatchInfo Class

using System;
using System.Runtime.ExceptionServices;

namespace ExceptionDispatchInfoExample
{
    class Program
    {
        static void Main()
        {
            ExceptionDispatchInfo myException = null;

            try
            {
                int number = 0;
                int myInt = 6 / number;
            }
            catch (DivideByZeroException ex)
            {
                myException = ExceptionDispatchInfo.Capture(ex);
            }

            if (myException != null)
            {
                myException.Throw();
            }
        }
    }
}

Top




Creating Custom Exceptions

"Custom exceptions should inherit from System.Exception ... custom exceptions should never inherit from System.ApplicationException. The System.ApplicationException class has become obsolete."

Situations may arise where an exception is needed that is more specialized than the exceptions provided by the .NET framework. Custom exceptions can be created for those situations. With custom exceptions extra data can be stored in the properties to help diagnose the error. Microsoft published guidelines for creating custom exceptions In the article Designing Custom Exceptions. Some of the recommendations for creating custom exceptions include:

  1. Derive exceptions from System.Exception or one of the other common base exceptions. Do NOT derive exceptions from System.ApplicationException.
  2. Use an 'Exception' suffix for the class name of the custom exception.
  3. Make exceptions serializable. An exception must be serializable to work correctly across application domain and remoting boundaries.
  4. Avoid deep exception hierarchies.
  5. Provide (at least) the four common constructors on all exceptions.
  6. Consider providing exception properties for programmatic access to extra information (besides the message string) relevant to the exception.
  7. Store useful security-sensitive information only in private exception state. Ensure that only trusted code can get the information.

Visual Studio provides the Exception code snippet for generating the shell code for a custom exception. The program below creates a custom LoginFailedException and calls the exception from a simulated log in failure.

Program using a Custom Exception

using System;
using System.Runtime.Serialization;

namespace CustomException
{
    class Program
    {
        // Custom Exception for Login Error
        [Serializable]
        public class LoginFailedException : Exception, ISerializable
        {
            public string UserName { get; set; }
            public string FailureCode { get; set; }

            public LoginFailedException(string userName, string failureCode)
            {
                UserName = userName;
                FailureCode = failureCode;
            }
            public LoginFailedException(string userName, string failureCode, string message)
                : base (message)
            {
                UserName = userName;
                FailureCode = failureCode;               
            }
            public LoginFailedException(string userName, string failureCode, string message, Exception innerException)
                : base (message, innerException)
            {
                UserName = userName;
                FailureCode = failureCode;               
            }

            protected LoginFailedException(string userName, string failureCode, SerializationInfo info, StreamingContext context)
                : base (info, context)
            {
                UserName = userName;
                FailureCode = failureCode;                               
            }
        }

        // LogIn - Throw Exception
        public static void LogIn(string userName)
        {
            bool loginFailure = true;

            if (loginFailure)
            {
                LoginFailedException ex = new LoginFailedException(userName, "A6", "Login Exception");
                throw (ex);
            }
        }

        static void Main()
        {
            try
            {
                LogIn("Kevin");
            }
            catch (LoginFailedException ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine("User was: {0}", ex.UserName);
                Console.WriteLine("Failure Code was: {0}", ex.FailureCode);
                Environment.Exit(-1);
            }
            Console.WriteLine("Successful login at this point");
        }
    }
}

Top



Reference Articles

Top