Home > .NET, Exceptions, Visual Basic > When to use custom exceptions

When to use custom exceptions

Have you ever found yourself in a situation where there seems to be no exception type that is a fit for the exception you want to throw? In smaller, stand-alone projects it is easy to just ignore the problem and use a plain old, generic Exception. After all, you’re the only one who’s going to see it, right? For larger, more complex projects or for class libraries that will be consumed by other assemblies that may not be written by you it can be useful or necessary to throw exceptions that have targeted meaning. The solution is to make your own, and the .NET framework has provided us the means.

Actually, the System.Exception class is a terrible choice to use when throwing errors because it is missing any contextual information about the issue. Yes, there is an exception message that can tell you something specific about the error, but the message is really about the last thing you want to use when trying to make a choice regarding how to identify and handle the exception. As a caller of the throwing method would you rather parse a message to see if the problem was a bad type cast or would you rather just receive an InvalidCastException? Yeah, me too.

The System.Exception class was intended to be used primarily as a base class for inherited types that are more targeted toward a specific scenario. .NET provides us with plenty of ready-to-use exception classes out of the box. For instance, the IndexOutOfRangeException occurs when… well, an index is out of range. A NullReferenceException occurs when a reference-type variable set to Nothing has a property or method accessed. Seeing a trend here? These exception classes are so targeted that the name itself can tell you what the problem is without having to even look at the message. And that is exactly how these exceptions are expected to be used most of the time. I say “most” because the Message property allows for further clarification when the identifying name is not quite enough. But even in this scenario the .NET framework provides a solution — just create a more targeted name. This is the case with ArgumentException which will inform the caller to a general issue regarding an argument to a class member. However if the issue is that the argument is null then you would be better served to use the ArgumentNullexception.

But what do you do when there is no obvious out-of-the-box exception class available that fits your specific scenario, like this example below.


Public Class Invoice

    Private _InvoiceNumber As Int32

    Public ReadOnly Property InvoiceNumber As Int32

        Get

            Return _InvoiceNumber

        End Get

    End Property

    Public Property Amount As Decimal

    '... other invoice properties below.

    Public Sub Save()

        'Perform some validations before sending information to the DB.

        If Amount <= 0 Then

            Throw New Exception("The amount must be greater than zero.")

        End If

    End Sub

    '...other functionality below.

End Class

Okay, let’s find the exception class that is right for it… got it yet? That’s okay – I’ve got plenty of time.

Now, you could use DataException because this involves data, but that choice is too vague and may be confused with exceptions bubbling up from the actual data operations beneath the Save call. I would want to know the difference (though is should be said that proper encapsulation would not allow raw data errors to bubble up past this Save method). This exception is generally about validation and specifically about having a value outside an expected range. These are the things we want the caller to know if the Save call is going to fail.

There are numerous ways we can approach this problem, some more complex than others. I will show you two of them: creating a simple custom exception class and then an extended one that provides some better context for the caller. Since we will be inheriting from it, let’s take a look at the base class for exceptions, the System.Exception class. For those of you who seem to recall something about using ApplicationException as a base class instead of Exception, this has been completely withdrawn as a recommended practice (see this and this).

I would like to point out a few good practices to keep in mind when cooking your own exceptions. First be aware that the ToString() function, inherited by all classes from the root Object class, will typically produce the entire stack trace of the exception so long as permissions allow it. You can choose to overrides this function and return whatever you like, but if you are relaying sensitive information from a security perspective you will likely want to restrict the scope of that information to only within acceptible and permissable roles.

System.Exception has four constructors to choose from. This may seem excessive but makes sense when we consider how they are used. Here’s a low-down on the three you should always include (constructor arguments in parentheses):

  1. (no argument) – Implies that the name itself will impart enough context to the caller.
  2. (String) – Allows for a message to be passed in to further clarify the problem.
  3. (String, Exception) – Used when wrapping another exception to facilitate a chain of exceptions.
  4. (SerializationInfo, StreamingContext) – used during serialization from a stream.

My rule of thumb is to always include all top three of these constructors when creating the derived class – the fourth is considered optional depending on serialization needs. It is easy to think you’ll never use the class in any other way than your one-time use, but such thinking can quickly lead to problems when you become a serial code re-user. Think about what would happen when another exception tries to wrap yours by using the #3 constructor. Blue squigglies are just not appropriate when you are trying to use a class for how it was intended. And if your excpeption class was in a referenced assembly it might reuire you to fix that class and reissue the library. Just not worth it for 20 seconds of extra coding.

So here’s what we have so far:


 Public Class RangeException

    Inherits System.Exception

    Public Sub New()

        MyBase.New("Value is out of range.")

    End Sub

    Public Sub New(message As String)

        MyBase.New(message)

    End Sub

    Public Sub New(message As String, ex As Exception)

        MyBase.New(message, ex)

    End Sub

End Class

Notice how I call the base constructor, passing along the arguments. This ensures that the inheritance chain is not broken. The one exception (no pun intended) is that for the argumentless constructor I pass in a default message for my class. This ensures that at least some relevant message is registered in case the caller chooses not to provide any information. This default message can be anything you want but it should be simple and generic. Last, it is always good practice to have the word Exception at the end of your class name. This is consistent with .NET practices and will cue the developer immediately to what the class is for without having to read intellisense comments.

We could be done here but let’s look at a slightly enhanced Invoice class that has a PO Number property that must fall within a certain range – just for the sake of example, say, with a prefix falling between A and L. While we could easily use the RangeException in this case it would not take too much effort to extend our new exception class into a more narrowly derived class. Let’s call it PONumberRangeException.


 Public Class PONumberRangeException

    Inherits RangeException

    Public Sub New()

        MyBase.New("The PO Value must begin with a letter between A and L.")

    End Sub

    Public Sub New(message As String)

        MyBase.New(message)

    End Sub

    Public Sub New(message As String, ex As Exception)

        MyBase.New(message, ex)

    End Sub

End Class

This is how our Save() method’s validation code might look now…


        If Amount <= 0 Then

            Throw New RangeException("The Amount value must be greater than zero.")

        End If

        If String.IsNullOrWhiteSpace(PONumber) = False Then

            If PONumber.Chars(0) <= "A"c OrElse PONumber.Chars(0) >= "L"c Then

                Throw New PONumberRangeException()

            End If

        End If

One more somewhat common scenario that is worth mentioning is the possibility of adding properties to your exception class that enhance the meta data or provide some basic functionality. We may be stretching the authenticity of our Invoice class, but for the sake of example let’s say we want to let the caller know what the bad PO value was (a good reason does not immediately come to mind). In this scenario we need the property to be read-only because it is not appropriate for the recipient to be setting this value under any circumstance. If it is not settable then we have two options available to get the value into the exception. If this class is in a library then we could set the property’s setter access to Friend, meaning only classes within that library’s assembly can access it. This might look as such:


Public Class PONumberRangeException

    Inherits RangeException

    Private _PONumber As String

    Public Property PONumber As String

        Get

            Return _PONumber

        End Get

        Friend Set(value As String)

            _PONumber = value

        End Set

    End Property

    ...constructors below.

End Class

However, this may not be an optimal solution if this class was ever to be inherited from again, which is a real possibility once we start an inheritance chain. An external assembly would not have access to the setter method and would not be able to set the PO Number property. A different solution is to just include it in the constructors as an optional argument but it would need to be in all the constructors. I do not like this solution because I consider the Exception class constructors to be something reliably consistent. So I guess the third option is start with the first option (Friend setter) and then make this a terminal derivation of the RangeException class (or sealing the class). Do this by marking the class as NotInheritable.


Public NotInheritable Class PONumberRangeException

    Inherits RangeException

    Private _PONumber As String

    Public ReadOnly Property PONumber As String

        Get

            Return _PONumber

        End Get

    End Property

    ...constructors below.

End Class

If someone wants to extend this functionality they will be forced to make their own version of the exception by inheriting from RangeException.

For additional information on general practices with exceptions please check out Best Practices for handling Exceptions in MSDN. Also, see Choosing the Right Type of Exception to Throw for some good tips.

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: