Custom Exceptions Without Constructor Tedium
This technique makes defining custom exceptions so easy you'll never be lazy and use System.Exception.
Introduction
We've all done it: deep in thought while in the middle of crafting some awesome code and you need to throw an exception, but it's tedious to go create a custom exception class with 4 forwarding constructors; laziness takes over and you just throw a new System.Exception
with some lame message. Now your exception isn't selectable in a try
-catch
and the next developer needing to act on your exception is going to be irritated with your laziness.
Creating a custom exception isn't a hard thing to do, but I'm opposed to repeating arbitrary forwarding constructors every time I want a custom exception. Especially when the only thing different with every custom exception I've ever created is just the name because I rarely need custom properties on them. Fortunately, there is a way to make it easy!
Inspiration
A few months ago, I was lamenting the tedium of creating custom exception classes and had an epiphany: what if we could leverage generics? It didn't take me long to have a fully working solution to this nagging problem. The resulting code is short and elegant, and usage is so easy I'll probably never use System.Exception
ever again!
Throwing a Custom Exception, the Easy Way
Using this technique is easy. Whenever you need to throw an exception, throw a new instance of ExceptionOf<T>
, with T
being any type you want. The type (class
, struct
, interface
or enum
) can be anything that's related to where you're throwing from, or it can be a type you define to represent a particular type of error.
throw new ExceptionOf<SomeType>(…args…);
I often use the class that had the exception as the type I am throwing if it's a general error from the class. When I need to throw an exception while in a switch
on an enum
, I like to throw the enum
type so I know what the error is related to. If I'm throwing a very specific kind of error, I will define a simple type with a descriptive name and an empty body to represent the error. It's exactly the same and just as easy as using System.Exception
, but you now have discretely catchable exception types.
This mechanism is so flexible, you could even use native types for T
, like object
or int
, but those don't convey any meaning about the exception so they aren't recommended.
What's also nice about this technique is all of the normal exception features and behaviors work the same as before: ExceptionOf<T>
does everything System.Exception
can do, and can be handled in the same way by anything that expects System.Exception
. That means it has inner exceptions and can itself be an inner exception, stack traces behave the same, and System.AggregateException
can contain them just like any other exception.
In my implementation that I will show later, all 4 of the same constructor overloads provided by System.Exception
are available for ExceptionOf<T>
. In your implementation, you can choose to exclude some of those, or add new ones to fit your needs.
How This Works
By using generics in ExceptionOf<T>
, we are able to use T
as a marker to distinguish the exception. An ExceptionOf<ClassA>
is distinctly different than ExceptionOf<ClassB>
and they can be caught independently. They can also be caught together as ExceptionOfBase
since they both inherit from it, or as System.Exception
since they ultimately inherit from that.
Because ExceptionOf<T>
just needs a type to distinguish it from other exceptions, any custom types you define for this purpose don't need any constructors. They are short one-liner definitions and easily grouped together in a single file in your project:
//examples of dedicated types for use with ExceptionOf<T>
namespace MyProject.CustomExceptions {
public interface ReallyBadError {}
public class EvenWorseError {}
public struct SevereError {}
}
Examples
Building on the custom exceptions in the previous block, here is an example showing several different ways we can throw ExceptionOf<T>
:
using MyProject.CustomExceptions;
namespace MyProject {
public enum Clouds {
None, Cumulus, Stratus, Stratocumulus, Swirling, Unknown
}
public class TestClass {
public void CloudMethod( Clouds cloudType ) {
switch ( cloudType ) {
case Clouds.Cumulus:
//using a dedicated interface for the exception type
throw new ExceptionOf<ReallyBadError>( "That shouldn't have happened!" );
case Clouds.Stratus:
//using a dedicated class for the exception type
throw new ExceptionOf<EvenWorseError>( "Oh no!!" );
case Clouds.Stratocumulus:
//using the class we are in as the exception type
throw new ExceptionOf<TestClass>( "Stratocumulus cloud!" );
case Clouds.Swirling:
//using a dedicated struct for the exception type
throw new ExceptionOf<SevereError>( "Tornado!" );
case Clouds.None:
//using a native type (not recommended, but shown as an example)
throw new ExceptionOf<object>( "Nothing happening in the sky." );
default:
//using the enum itself as the exception type
throw new ExceptionOf<Clouds>( "I don't know what kind of cloud that is!" );
}
}
}
}
The natural thing to do next is catch these exceptions. It works exactly the way you would expect:
public void TestMethod() {
try {
CloudMethod( Clouds.Stratus );
} catch ( ExceptionOf<ReallyBadError> ex ) {
//explicit catch of custom error based on interface type
} catch ( ExceptionOf<EvenWorseError> ex ) {
//explicit catch of custom error based on class type
} catch ( ExceptionOf<SevereError> ex ) {
//explicit catch of custom error based on struct type
} catch ( ExceptionOf<TestClass> ex ) {
//explicit catch of custom error based on the class we threw from
} catch ( ExceptionOf<Clouds> ex ) {
//explicit catch of custom error based on the Clouds enum we threw from
} catch ( ExceptionOfBase ex ) {
//catch any other CustomException<T> we didn't have in a catch block above
} catch ( Exception ex ) {
//catch any other exception not caught above
}
}
Of particular note in that code above, you can see that we are able to catch ExceptionOfBase
to handle any ExceptionOf<T>
that wasn't already caught. This is handy if you only care that it was a custom exception but not what generic type.
Another handy capability is having a base class throw exceptions on behalf of an inheriting class. For example, if you have a generic base class with one of the generic types being the type of the inheriting class and you want to throw a general exception of the inheriting class type, you can use the type parameter:
public class InheritedClass : GenericBase<InheritedClass> {
//inherits SomeMethod()
}
public abstract class GenericBase<TSubClass> {
public void SomeMethod() {
//throw exception on behalf of the subclass that inherited this class
throw new ExceptionOf<TSubClass>();
}
}
The Implementation
The code is deceptively simple, yet powerfully convenient. My favorite kind of code! If you're looking for a download, there's no need...these two classes are the only thing you need in your project to use this technique:
public abstract class ExceptionOfBase : Exception {
protected ExceptionOfBase()
: base() { }
protected ExceptionOfBase( string message )
: base( message ) { }
protected ExceptionOfBase( string message, Exception innerException )
: base( message, innerException ) { }
protected ExceptionOfBase( SerializationInfo info, StreamingContext context )
: base( info, context ) { }
}
public class ExceptionOf<T> : ExceptionOfBase {
public ExceptionOf()
: base() { }
public ExceptionOf( string message )
: base( message ) { }
public ExceptionOf( string message, Exception innerException )
: base( message, innerException ) { }
public ExceptionOf( SerializationInfo info, StreamingContext context )
: base( info, context ) { }
}
Something to consider in your choice of where you want these 2 classes in your solution: you can put them in a common library or each project can define them locally. One scenario where it might be useful to define them locally rather than in a common library is if you have different projects throwing the same type for T
but you need to have selectivity in a catch
block for one project vs. another. By defining them locally under a project's namespace
, that namespace
becomes part of the fully qualified type and MyProjectA.ExceptionOf<T>
can be identified as being different from MyProjectB.ExceptionOf<T>
even if T
is the same.
Advantages, Disadvantages & Considerations
One thing to remember when using this technique:
- Although you can throw any type, it must be declared
public
if there is any chance of it bubbling up to a caller outside of your assembly- This is an easy thing to forget when throwing from inside a non-
public
class and you want to use the class type itself for the custom exception
- This is an easy thing to forget when throwing from inside a non-
I'm sure you already noticed all the advantages, but here is a nice list (because I am a list guy):
- Custom exceptions can now be DRY (Don't Repeat Yourself) because we aren't repeating all the constructor forwarders
- In some cases, you might not need to create a custom type because you can use any type at hand that has meaning for the exception being thrown
- Allows developers to have custom exceptions and still be lazy!
- Dedicated types representing exceptions are very quick to create because they have empty bodies, and they can easily be grouped together in the same file in your project
- A generic base class that has a generic parameter identifying the class inheriting it can throw exceptions on behalf of the inheriting class
I've only found one thing so far that some might find as a disadvantage. Personally, I don't mind it.
-
Longer fully qualified type name for the exception because it's generic
- e.g.
MyProject.ExceptionOf`1[MyProject.SomeType]
- Typically only noticeable in logs
- e.g.
Another disadvantage pointed out in the article comments:
- Visual Studio debugger doesn't show the generic type in the exception dialog
- e.g. displays:
MyProject.ExceptionOf`1
- So far, only an issue while debugging in Visual Studio; admittedly very inconvenient
- Does not affect
ToString
method - My guess is the Visual Studio engineers never thought of the possibility of generic exceptions, so they display
Type.Name
, which doesn't include the generic type list - A workaround is to use the
Immediate window
to call one of these statements on the ambient$exception
while the debugger is stopped on the exception:? $exception.ToString()
? $exception.GetType().FullName
? $exception.GetType().GenericTypeArguments
- Feel free to comment on my feedback to the Visual Studio team to inspire more attention
- e.g. displays:
Got Feedback?
I encourage your feedback! Tell me stories of how it saved your day, or how it made you chase your tail. Tell me what challenges you ran into or if you made improvements. Found more advantages or disadvantages? Let me know!
Name Ideas?
Although I love the name ExceptionOf<T>
, I'm not as happy with the name ExceptionOfBase
. It feels clunky in a catch
block. I considered ExceptionOfAny
so that it would look nice in a catch
block, but when you are looking at it anywhere else, it doesn't make sense. If you have some good naming ideas, please share!
Go Forth and Use ExceptionOf<T>
I think this technique is so useful, it might catch on. If you like it, spread the word! I'm looking forward to the day I am doing a code review for someone and discover they learned about this technique from someone other than me. :)