Writing great C# exceptions

| 3 min. (639 words)

In this post I’m going to talk about exceptions and how you can craft better software by giving them appropriate design considerations. I’ve often found that developers, even experienced developers, don’t put much thought into the exception objects that they produce. This post is focused on C#, but it largely applies to all languages depending on the infrastructure provided for error handling.

1. Be specific

When an exceptional situation occurs and you wish to throw an exception for that state, what type of error should you throw?

When I was a junior developer, I’d frequently find an existing exception type and just use that. A common go to was ‘ArgumentOutOfRangeException‘ because that often was the exception that had occurred – a method had received a parameter that was outside of the bounds for whatever I was doing.

That’s totally fine for simple exceptions like a legitimate out of range exception.

Outside of the lower level, say, in your business logic or domain model, I’m a big believer in writing your own exception types.

It is fairly straight forward to quickly create your own exception types, inheriting from the base Exception type. You can then add additional properties as needed so that your exceptions are very specific to the given error. An example of this in the .NET framework is SqlException having a ‘Number‘ property that provides the error number provided from SQLServer.

2. Great error messages

With Raygun, we’ve literally seen billions of errors reported. We’ve seen the good, the bad and the ugly of error messages.

A common reason for bad messages is that we developers like to load the context into the message. For example, we won’t typically choose to throw an exception with “User not found.” as it doesn’t help us debug in a hurry. We’ll throw “User not found: fred@raygun.com, id: 2133”, giving us context on the error right there in the message.

That’s not a wise idea.

Firstly, it leaks implementation details to any users who might see the error message.

Secondly, there’s only so much context you can squeeze into the message.

Thirdly, there is a better way…

3. Use Exception.Data

Exception.Data is a dictionary property that exists on the base Exception type. It’s designed expressly for the purpose of adding context about what was happening at the time of the exception being raised.

This is a really nice, and quick, way to add context – especially if you’re not needing or wanting to go to the extent of writing your own exception classes with custom data properties.

As an aside, Raygun will serialise the Exception.Data collection so you can have it all within Raygun. Combine that with nicer messages (e.g. ‘User not found’) and the error groups get created with appropriate messages right from the start.

3. Use InnerException if needed

Along with writing your own business layer exceptions when desirable, it can be useful to wrap the lower level exceptions that triggered the exception in the first place. This helps trace what’s actually occurred and gives even more context. More context usually means being able to fix bugs faster.

Raygun fully supports reporting the inner exceptions with Raygun4Net also, so you can get the full story.

4. Only use exceptions in exceptional cases

This is more a general rule, but exceptions are supposed to be exceptional. They’re costly in terms of performance (about 8ms per exception on my fairly grunty home PC) and they break control flow making it difficult to understand the repercussions of an exception.

Exceptions should be exceptional.

5. Do something with them

OK, so this one is a bit cheeky – but if you do have exceptions being thrown, you want know about it.

That’s exactly why we built Raygun in the first place: so you can see the health of your apps, and then fix things.