Click here to Skip to main content
15,895,799 members
Articles / Programming Languages / C#

How to Design Exceptions

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
8 Apr 2013LGPL36 min read 11.3K   11   2
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:

C#
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:

C#
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 are the most specific ones. Hence, any more generic exceptions don’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 communicate 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 <a>ApplicationException</a> 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 quite a 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 its own chapter (see below).

Sample Exception

So you should at least have the following code:

C#
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:

C#
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.

C#
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:

C#
[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 provides relevant context information when creating the exception. It’s also your responsibility to make sure that the information is visible after deserialization and when ToString() is called.

This article was originally posted at http://blog.gauffin.org/2013/04/how-to-design-exceptions

License

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


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
GeneralWhere to handle exceptions Pin
Klaus Luedenscheidt8-Apr-13 19:44
Klaus Luedenscheidt8-Apr-13 19:44 
GeneralRe: Where to handle exceptions Pin
jgauffin8-Apr-13 20:07
jgauffin8-Apr-13 20:07 

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

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