Exception handling

This article is part of our exception handling article series.
Learn more about exception handling

Exceptions explained

The previous article explained the purpose of exceptions and their benefits. This article aims at describing exception classes and how you use them.

A gentle introduction

We will go through the exception class in detail soon, but let's start with a small usage example. Everything will be explained in detail later in the article.

try
{
    throw new InvalidOperationException("Failed to load resource 'Hello world!'.");
}
catch (InvalidOperationException exception)
{
    Console.WriteLine(exception.Message);
}

A new exception object of the type InvalidOperationException is created in the above example. It's constructed with an error message stating that the resource could not be loaded. The purpose of the error message is to explain why we decided to throw an exception.

Next, we use the throw keyword with the created exception object. throw tells .NET to stop running the code and send the exception up the call stack until it finds a catch block in any of the invoked methods. That is also called exception propagation.

Finally, we have a try/catch block. The purpose of the catch block is to tell .NET that we want to handle a specific type of exception. The exception can have been thrown inside the try block or any called method within the try block.

The output from the application will be the following.

Conclusion

  • The message in the exception constructor should be used to explain why an exception was thrown.
  • throw is used to abort the current execution flow when a method cannot deliver what it promised.
  • try is used to tell which code any potential exceptions should be handled for.
  • catch is used to handle exceptions, including all exceptions that inherit the specified one.

Exception classes

Exception classes are used to represent exceptions. The base class, Exception, defines some properties that all exceptions will share.

Exception class names should always end with Exception per .NETs naming conventions, like InvalidDataException or SecurityException.

There are two properties on the Exception class that are more important than the other ones. Those two are specified below, to learn about the rest of the properties, read the documentation in MSDN.

Message

The first property is the Message property since it typically explains why the exception was thrown.

The property value is in most cases specified in the constructor of an exception:

new KeyNotFoundException("Failed to find the given key `meta` in the dictionary.");
new EntityNotFoundException("User 'Lars' was not found.");
new ArgumentException("UserId must be alphanumeric.");

StackTrace

The stack trace is like breadcrumbs. It tells where in the code that the exception originated from. The first line in the stack trace is the method that the exception was thrown in. The next line is the method that called the failing method and so on.

Let's take the earlier code example, but with complete source and with the stack trace printed instead.

using System;

namespace Exception1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                throw new InvalidOperationException("Failed to load resource 'Hello world!'.");
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
        }
    }
}

The code will generate the following output:

at Exception1.Program.Main(String[] args) in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 11

The output shows that the exception was thrown in the Main method in the class Program.cs. The line number is included since this application had the pdb file distributed with it.

Conclusion

  • Exceptions are classes that inherit the Exception base class.
  • The StackTrace property shows where in the application that the exception was thrown and how that method was called.
  • The Message property specifies why an exception was thrown.
  • To get line numbers in the stack trace, include all pdb files from the build output.

Exception propagation

The stack trace is essential when wanting to understand where an exception was thrown. It shows which path the code took in the application before the exception was thrown. Having that knowledge makes it easier to reproduce the cause of the generated the exception.

The stack trace is never prepopulated but is instead appended each time the .NET exception handling mechanism travels up the call stack. That process is called exception propagation.

Consider the following example:

var myException = new InvalidOperationException("Failed to load resource 'Hello world!'.");
Console.WriteLine(myException.StackTrace);

The output from exception.StackTrace will be null because it's the throw statement that starts to fill the stack trace.

Wrapping the example in a try/catch will populate the stack trace:

using System;

namespace Exception1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var myException = new InvalidOperationException("Failed to load resource 'Hello world!'.");
                throw myException;
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
        }
    }
}

Output

at Exception1.Program.Main(String[] args) in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 12

Likewise, calling a method from the try block will produce two lines in the stack trace.

using System;

namespace Exception1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AnotherMethod();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
        }

        public static void AnotherMethod()
        {
            throw new InvalidOperationException("Failed to load resource 'Hello world!'.");
        }
    }
}

Output

at Exception1.Program.AnotherMethod() in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 21
at Exception1.Program.Main(String[] args) in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 11

Observe that the Main() method is last in the stack trace. It's because it was appended as the stack unwinds from the deepest call to the first one.

Having three calls works the same.

using System;

namespace Exception1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AnotherMethod();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
        }

        public static void AnotherMethod()
        {
            DeepestMethod();
        }

        public static void DeepestMethod()
        {
            throw new InvalidOperationException("Failed to load resource 'Hello world!'.");
        }
    }
}

Output

at Exception1.Program.DeepestMethod() in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 26
at Exception1.Program.AnotherMethod() in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 21
at Exception1.Program.Main(String[] args) in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 11

DeepestMethod did not have a try block, nor did AnotherMethod but Main did. Thus the exception traveled three steps before getting to a catch block.

Having a catch block inside AnotherMethod would stop the exception propagation:

using System;

namespace Exception1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AnotherMethod();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
        }

        public static void AnotherMethod()
        {
            try
            {
                DeepestMethod();
            }
            catch (Exception exception)
            {
                Console.WriteLine("Handled in AnotherMethod: " + exception.Message);
            }
        }

        public static void DeepestMethod()
        {
            throw new InvalidOperationException("Failed to load resource 'Hello world!'.");
        }
    }
}

Output

Notice that the console writes from Main is not printed, since exception propagation stops at the first found catch block.

Conclusions

  • Exception propagation is when the exception travels up the method calls (i.e. stack frames).
  • The stack trace is appended for each method call that the exception travels to.
  • Exception propagation stops when a catch block is found.

Rethrowing exceptions

Sometimes you want to catch exceptions to be able to process them in some way but still let the upper stack frames be able to handle the same exception.

That can be achieved by having another throw in the catch block.

using System;

namespace Exception1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AnotherMethod();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
        }

        public static void AnotherMethod()
        {
            try
            {
                throw new InvalidOperationException("Sample exception");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception was here: " + ex);
                Console.WriteLine();
                throw;
            }
        }
    }
}

Output

Exception was here: Sample exception

at Exception1.Program.AnotherMethod() in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 28
at Exception1.Program.Main(String[] args) in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 11

The first two lines are from the Console.WriteLine calls in AnotherMethod and the stack trace is from Console.WriteLine in Main().

Using throw in catch blocks tells .NET that it should continue with the exception propagation.

Avoid throw ex; when rethrowing exceptions

Do not use throw exception; in catch blocks:

try
{
    throw new InvalidOperationException("Sample exception");
}
catch (Exception ex)
{
    throw ex;
}

Using throw ex; tells .NET that it should start a new exception propagation using your previously created exception object. As in such, the stack trace will be cleared and built from scratch again.

A complete example:

using System;

namespace Exception1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AnotherMethod();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
        }

        public static void AnotherMethod()
        {
            try
            {
                DeepestMethod();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception was here: " + ex.Message);
                Console.WriteLine();
                throw ex;
            }
        }

        public static void DeepestMethod()
        {
            throw new InvalidOperationException("Failed to load resource 'Hello world!'.");
        }
    }
}

Output

Exception was here: Failed to load resource 'Hello world!'.

at Exception1.Program.AnotherMethod() in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 28
at Exception1.Program.Main(String[] args) in C:\src\junk\Exceptions\Exception1\Exception1\Program.cs:line 11

See? The DeepestMethod is nowhere to be found in the stack trace, making it impossible to find where the exception originated from.

Conclusion

  • Rethrowing can be done to process the exception before letting it continue to travel up the call stack.
  • Avoid throw ex as it tells .NET to treat the exception object as a new exception.