Click here to Skip to main content
15,880,405 members
Articles / Programming Languages / C#

Custom Exceptions Without Constructor Tedium

Rate me:
Please Sign up or sign in to vote.
4.98/5 (19 votes)
9 Oct 2017CPOL7 min read 23.3K   16   27
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.

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

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

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

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

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

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

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
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

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. :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I first learned programming on an Apple II when I was in 7th grade by reading a mountain of back issues of Byte magazine and spending every possible opportunity in the computer lab. I only got deeper into programming from there.

My first 9 adult years were in service to my country doing my other favorite thing: electronics. After that I started my software career using VB6, classic ASP and SQL Server. Shortly after .NET was introduced I switched to C# and never looked back. These days I'm focused on MVC Core and Entity Framework Core. Along the way I have dabbled in some other languages and frameworks, but I keep coming back to the Microsoft stack as my core platform because nothing else I've tried has come close to matching it for capability and tooling. There are a few excellent non-Microsoft frameworks I use to supplement my work as well, but my root is C# and SQL Server.

For me, developing software is more than just a skill: it is a craft that I am passionate about. I find great satisfaction in finding elegant and efficient ways to solve problems, both in software and in business processes.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Jim_Snyder11-Oct-17 3:55
professionalJim_Snyder11-Oct-17 3:55 
GeneralRe: My vote of 5 Pin
edge942111-Oct-17 7:26
edge942111-Oct-17 7:26 
QuestionReally nice, and here's an enhancement Pin
bjoho10-Oct-17 8:33
bjoho10-Oct-17 8:33 
AnswerRe: Really nice, and here's an enhancement Pin
edge942111-Oct-17 6:45
edge942111-Oct-17 6:45 
GeneralRe: Really nice, and here's an enhancement Pin
bjoho11-Oct-17 7:32
bjoho11-Oct-17 7:32 
SuggestionName suggestion Pin
Dino Miniutti9-Oct-17 19:39
Dino Miniutti9-Oct-17 19:39 
GeneralRe: Name suggestion Pin
edge942111-Oct-17 6:31
edge942111-Oct-17 6:31 
QuestionNice! Pin
Rob Philpott9-Oct-17 6:50
Rob Philpott9-Oct-17 6:50 
Your first paragraph certainly rings bells. I could be one of those guilty of not creating exceptions enough, or half baked ones.

A quick query though regarding serialization. I seem to remember exception classes are normally marked with the Serializable attribute, which is probably not strictly necessary.

In the WCF world, sometimes exceptions are wrapped up in a FaultException and returned to the caller. Will this work without additional effort using your approach?

I kind of suspect it will. Presumably it depends on the serializability of the generic type, but in the case of WCF the need to paste [DataContact] and [DataMember] all over everywhere seems to have diminished in more recent frameworks.

But then you get into all that [KnownType] business on your abstract base. Hmm. Too late in the day for me to think too much about it right now.
Regards,
Rob Philpott.

AnswerRe: Nice! Pin
edge94219-Oct-17 9:47
edge94219-Oct-17 9:47 
GeneralRe: Nice! Pin
Rob Philpott10-Oct-17 5:14
Rob Philpott10-Oct-17 5:14 
GeneralRe: Nice! Pin
edge942111-Oct-17 7:25
edge942111-Oct-17 7:25 
QuestionMissing Context in Exception Box while Debugging Pin
Member 126544855-Oct-17 6:15
professionalMember 126544855-Oct-17 6:15 
AnswerRe: Missing Context in Exception Box while Debugging Pin
edge94215-Oct-17 18:44
edge94215-Oct-17 18:44 
AnswerRe: Missing Context in Exception Box while Debugging Pin
edge94219-Oct-17 9:35
edge94219-Oct-17 9:35 
GeneralRe: Missing Context in Exception Box while Debugging Pin
edge94219-Oct-17 12:49
edge94219-Oct-17 12:49 
GeneralRe: Missing Context in Exception Box while Debugging Pin
edge94219-Oct-17 17:08
edge94219-Oct-17 17:08 
GeneralGreat article! Pin
Philip Blignaut3-Oct-17 3:31
Philip Blignaut3-Oct-17 3:31 
GeneralRe: Great article! Pin
edge94219-Oct-17 11:19
edge94219-Oct-17 11:19 
QuestionJust a little further.... Pin
Irene Troupansky2-Oct-17 7:20
Irene Troupansky2-Oct-17 7:20 
AnswerRe: Just a little further.... Pin
edge94212-Oct-17 7:58
edge94212-Oct-17 7:58 
QuestionDo you need ExceptionOfBase? Pin
jsc421-Oct-17 23:28
professionaljsc421-Oct-17 23:28 
AnswerRe: Do you need ExceptionOfBase? Pin
wmjordan2-Oct-17 0:15
professionalwmjordan2-Oct-17 0:15 
AnswerRe: Do you need ExceptionOfBase? Pin
edge94212-Oct-17 7:54
edge94212-Oct-17 7:54 
GeneralRe: Do you need ExceptionOfBase? Pin
jsc424-Oct-17 3:38
professionaljsc424-Oct-17 3:38 
PraiseMy vote of 5 Pin
Сергій Ярошко1-Oct-17 1:09
professionalСергій Ярошко1-Oct-17 1:09 

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.