This article is a part of a series about exception handling.

Preface

There are lots of info regarding this topic on the Internet. Also, each and every C# book has a few pages dedicated to this subject. I am going to start from basics and continue with more advanced topics related to IoC containers and handling exceptions in SOA and distributed systems. I am going to highlight some common mistakes related to implementing exception handling as well.

Basics

Very little software really gets exception handling right. Even many critical, backend server systems tend to have a very poor design when it comes to exception handling. There are still a lot of debates about error codes vs exceptions. You can read more at Ned Batchelder: Exceptions vs status returns. Joel Spolsky also has an opinion why I don't like programming with exceptions

But we will pretend that exceptions is the best way to handling errors :).

There will be a set of examples based on a behaviour of a fictional resource planning application. We will have two entities 'Resource' and 'Project'.

Consider the following code as a starting point:

public class Program
{
    static void Main(string[] args)
    {
        try
        {
            var resource = new Resource("resource1");
            resource.CheckOut("user 1");
            resource.CheckOut("user 2");
        }
        catch(Exception ex)
        {
            Console.WriteLine($"Sorry, something went wrong: {ex.Message}");
        }
    }
}

public class Resource
{
    private string CheckedOutBy;
    private DateTime CheckedOut;

    public Resource(string title)
    {
        Title = title;
    }

    public string Title { get; private set; }

    public void CheckOut(string user)
    {
        if (string.IsNullOrEmpty(user))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (!string.IsNullOrEmpty(CheckedOutBy))
        {
            throw new Exception($"The resource '{Title}' has been checked out by '{CheckedOutBy}' at '{CheckedOut}'. Find that damn user and make him check-in his work!");
        }

        CheckedOutBy = user;
        CheckedOut = DateTime.Now;
    }

    public void CheckIn()
    {
        CheckedOutBy = null;
    }
}

We have a resource that can be checked-out and checked-in. Resource has to be checked-in before it can be checked-out, otherwise an exception will be thrown. You have to pass a non-empty user name as a parameter, otherwise an another exception will be thrown.

In any case, lets start with the basics. Ordinary C# developer should know the following:

When you want to throw an error, you first need to create a new instance of an exception. You should not try to reuse exception objects. Each time you throw an exception, you should create a new one, especially when working in a multithreaded environment, the stack trace of your exception can be changed by another thread.

So we use code like this:

throw new ArgumentNullException(nameof(user));

...

throw new Exception($"The resource '{Title}' has been checked out by '{CheckedOutBy}' at '{CheckedOut}'. Find that damn user and make him check-in his work!");

You may already know that:

Once throwing an exception becomes necessary, it’s best to use the exceptions defined in the .NET Framework. But there are situations in which you want to use a custom exception. This is especially useful when developers working with your code are aware of those exceptions and can handle them in a more specific way than the framework exceptions.

That's why we should throw a custom exception. If you want to handle the error in a some specific way, lets say you want to notify consumers of your code that some operation has failed and you have an additional information about this inside your nice custom exception, then you should implement a custom exception. A custom exception should inherit from System.Exception, that's why by convention, you should use the Exception suffix in naming all your custom exceptions. . You should never inherit from System.ApplicationException. The original idea was that all C# runtime exceptions should inherit from System.Exception and all custom exceptions from System.ApplicationException. However, because the .NET Framework doesn’t follow this pattern, the class became useless and lost its meaning. You need to provide at least a parameterless constructor. It’s also a best practice to add a few other constructors: one that takes a string and one that takes both a string and an exception.

The constructor for serialization was deprecated in NET Core 1.0, so we won't use it in our example.

Also, we can add a few custom properties to our brand new exception to make it more usable. Exposing this data through properties can help users of your exception inspect what has gone wrong.

It’s also important to make your exception serializable, which makes sure that your exception can be serialized and works correctly across application domains (for example, when a web service returns an exception). In full .NET you can use the [Serializalbe] attribute, but in NET Core binary serialization isn't available so you can't use [Serializable]. This subject will be covered in on of the next posts.

So it should look like this:

public class CheckedOutByAnotherUserException : Exception
{
    public string Resource { get; private set; }
    public string CheckedOutBy { get; private set; }
    public DateTime CheckedOut { get; private set; }

    public CheckedOutByAnotherUserException(string resourceTitle, string checkedOutBy, DateTime checkedOut)
    {
        Resource = resourceTitle;
        CheckedOutBy = checkedOutBy;
        CheckedOut = checkedOut;
    }

    public CheckedOutByAnotherUserException(string resourceTitle, string checkedOutBy, DateTime checkedOut, string message) : base(message)
    {
        Resource = resourceTitle;
        CheckedOutBy = checkedOutBy;
        CheckedOut = checkedOut;
    }

    public CheckedOutByAnotherUserException(string resourceTitle, string checkedOutBy, DateTime checkedOut, string message, Exception inner) : base(message, inner)
    {
        Resource = resourceTitle;
        CheckedOutBy = checkedOutBy;
        CheckedOut = checkedOut;
    }
}

Let's modify CheckOut method:

public void CheckOut(string user)
{
    if (string.IsNullOrEmpty(user))
    {
        throw new ArgumentNullException(nameof(user));
    }
    
    if (!string.IsNullOrEmpty(CheckedOutBy))
    {
        throw new CheckedOutByAnotherUserException(Title, CheckedOutBy, CheckedOut);
    }

    CheckedOutBy = user;
    CheckedOut = DateTime.Now;
}

You may noticed that I removed that ugly hardcoded message from the method. Make sure you don't have error messages inside your business logic. The message parameter is not intended for passing error messages to end users. It should be used to simplify debugging by developers. It is a good idea to write that message to the log.

We can also update the try-catch block:

try
{
    var resource = new Resource("resource1");
    resource.CheckOut("user 1");
    resource.CheckOut("user 2");
}
catch (CheckedOutByAnotherUserException ex)
{
    Console.WriteLine($"The resource '{ex.Resource}' has been checked out by '{ex.CheckedOutBy}' at '{ex.CheckedOut}'. Find that damn user and make him check-in his work!")
}
catch (Exception ex)
{
    Console.WriteLine($"Sorry, something went wrong: {ex.Message}");
}

Now it looks much better!

Summary

There are few rules that every developer needs to follow:

  • Use exceptions instead of error codes
  • Use built-in exceptions when possible
  • Use custom exceptions when you need to have a more complex exception handling logic
  • Use custom exceptions when you need to pass some data with the exception to the calling code
  • Build error messages for users based on data in the exception, do not use message parameter to pass error message to users.

;