Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

How to design exceptions

, 8 Apr 2013 LGPL3
Rate this:
Please Sign up or sign in to vote.
This article will teach you how you should design your exception classes.

This article will teach you how you should design your exception classes. It’s a follow up to my previous article which told you what exceptions are and their intended usage.

Exception inheritance

As C# is an object oriented language exceptions also use inheritance. You might wonder why since exceptions doesn’t benefit from inheritance if you view it from a strict exception handling view. Consider the following code:

try
{
    // DB related code
    return userFetchedFromDb;
}
catch (DataException ex)
{
    // Could you in any way guarantee that you can return a user
	// by catching the DataException?
}

It’s unlikely that you could return a User by catching DataException. However, if you have had something like:

try
{
    // DB related code
    return userFetchedFromDb;
}
catch (DataConnectionFailedException ex)
{
    // you could try to connect again 
	// here and throw if second attempt fails too.
	//
	// that only makes sense if the 
	// DB connections are known to be instable.
}

You could have had a chance to recover from the exception.

The only exceptions that are useful for exception handling is the most specific ones. Hence any more generic exceptions doesn’t add any value.

So why do they exist then? I can come up with three reasons:

  1. Exception hierarchies allow you to categorize exceptions which makes it easier to log or notify when exceptions are caught.
  2. Exception hierarchies communicates intended usage. You won’t throw an exception derived from IOException when dealing with a math calculations
  3. It makes it easier to find the correct exception to use. Most documentation tools can generate links between all parent and sub classes which makes it easy to find the most relevant exception.

What I’m saying is that exception inheritance doesn’t help you when handling exceptions in your code (to try to rescue the situation). They do however help when you are trying to figure out what went wrong and why.

ApplicationException

If you read the official documentation for Exception you’ll see that they recommend you to inherit ApplicationException for any user-defined exception. Do not do that. Ask yourself what benefit it would give you to know that the exception is user defined? Could you do something differently in your code if you knew that the caught exception is defined by you? To make it worse, “user-defined” also includes ALL exceptions which are not defined in .NET (including those in your favorite library).

But to be fair Microsoft states the same thing in their best practices and in the ApplicationException page.

Conclusion

Try to inherit the most specific exception which works for your use case (which of course can be just Exception). And don’t be afraid to inherit exceptions from the framework itself.

Constructors

The next thing to do is to create a few constructors for your very own exception. Here is the minimal amount of constructors that you should include:

YourException(string description)

The description is very important. I usually include context information in it since it makes it a lot easier to find why an exception occurs.

Take for instance the KeyNotFoundException which is thrown by the Dictionary class when you try to access an item which is not found in the dictionary. All you get is a message saying “The given key was not present in the dictionary.” The side effect of that is that you have to increase your own logging for exceptional cases to be able to find out what went wrong.

It would have helped tremendously if the exception message would have been “The key ‘abrakadabra’ was not present in the dictionary.”.

For your own sake, include as much context information you can.

YourException(string description, Exception inner)

This constructor is the same as the previous one, but is used when you catch one exception and throw another one. It can be a quite useful technique to be able to get exceptions which is relevant to the current use case (but still provide information about the exception that triggered the failure).

I’ll come back to throwing exceptions in the next blog post.

YourException(SerializationInfo info, StreamingContext context)

This constructor is required if you want to support serialization (i.e. transfer exceptions between different processes / applications or just save it to a file).

Serialization of exceptions deserve it’s own chapter (see below).

Sample exception

So you should at least have the following code:

public class SampleException : Exception
{
    public SampleException(string description)
        : base(description)
    {
        if (description == null) throw new ArgumentNullException("description");
    }

    public SampleException(string description, Exception inner)
        : base(description, inner)
    {
        if (description == null) throw new ArgumentNullException("description");
        if (inner == null) throw new ArgumentNullException("inner");
    }

    public SampleException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
    }
}

Do note that I just pass the information to the base class.

Please don’t have a default constructor. At least not a public one. An exception without even the simplest context information is plain worthless. It also tells you that something is wrong, but without any given context.

Forcing context information

Context information will always aid you tremendously when trying to prevent exceptions in the future (i.e. you have caught an exception and want to try to figure out why it happened).

You can force context information by specifying mandatory fields in the constructors. For instance, if you want the user to provide the HTTP status code for an HTTP exception provide the following constructors:

public class HttpException : Exception
{
	System.Net.HttpStatusCode _statusCode;
	
    public SampleException(System.Net.HttpStatusCode statusCode, string description)
        : base(description)
    {
        if (description == null) throw new ArgumentNullException("description");
		_statusCode = statusCode;
    }

    public SampleException(System.Net.HttpStatusCode statusCode, string description, Exception inner)
        : base(description, inner)
    {
        if (description == null) throw new ArgumentNullException("description");
        if (inner == null) throw new ArgumentNullException("inner");
		_statusCode = statusCode;
    }

    public SampleException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
    }
	
	public System.Net.HttpStatusCode StatusCode { get; private set; }

}

That code have two problems though:

ToString() is the king

How many of you readers the exception documentation for every possible exception that your code can throw? None I guess. Hence any kind of context information which is not displayed in exception.ToString() will be lost.

However since ToString() displays all information for the exception and not just the message you typically do not want to override it. There is a neat option though: Override the Message property (as shown below).

Serialization

The status code will not survive serialization. We need to make sure that it does. See the serialization chapter.

Serialization

The XmlSerializer is not supported when it comes to exceptions as the Exception.Data property is of type IDictionary. So if you had any plans of using that serializer with exceptions: FORGET IT! ;) You can instead use the DataContractSerializer in .NET 4 or the BinaryFormatter in earlier versions.

The only time you need to do anything about serialization (anything other than adding the constructor) is when you have custom properties or fields in your exceptions. You also have two options for that.

Use GetObjectData

With GetObjectData() you have to specify additional fields during serialization (using the method) and deserialization (using the constructor). This method works fine with both BinaryFormatter and DataContractSerializer.

public class HttpException : Exception
{
    // [...]

    public HttpException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
		// this is new
        StatusCode = (HttpStatusCode) info.GetInt32("HttpStatusCode");
    }

    public HttpStatusCode StatusCode { get; private set; }

    public override string Message
    {
        get { return base.Message + "\r\nStatus code: " + StatusCode; }
    }

	// this is new
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("HttpStatusCode", (int) StatusCode);
    }
}

The Data property

The other approach is to use the Data property that destroyed for us with the XmlSerializer. So it would be a shame to not use it, right? Simply change the custom property to wrap it:

[Serializable]
public class HttpException : Exception
{
    // [.. constructors here ..]

    public HttpStatusCode StatusCode
    {
        get { return (HttpStatusCode)Data["HttpStatusCode"]; }
        set { Data["HttpStatusCode"] = value; }
    }

    public override string Message
    {
        get { return base.Message + "\r\nStatus code: " + StatusCode; }
    }

 
}

That does however not work with the DataContractSerializer :)

Conclusion

If you want to use serialization, make sure that you have the (SerializationInfo info, StreamingContext context) constructor in which you load all custom properties, and also override GetObjectData(SerializationInfo info, StreamingContext context) to be able to supply the information during serialization.

Summary

As exceptions are very hard to handle (to deliver the result expected from the method) it’s your responsibility as an exception designer to make sure that the programmer provide relevant context information when creating the exception. It’s also your responsibility to make sure that that information is visible after deserialization and when ToString() is called.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralWhere to handle exceptions PinmemberKlaus Luedenscheidt8-Apr-13 20:44 
GeneralRe: Where to handle exceptions Pinmemberjgauffin8-Apr-13 21:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411023.1 | Last Updated 8 Apr 2013
Article Copyright 2013 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid