Sunday, January 3, 2010

Creating Custom Exceptions in .NET - Real World Scenarios

Here's an article I've written about custom exceptions. It's not only about custom exceptions per se, it's also about using them when you have interfaces.

Enjoy

Table of Contents



Introduction


The other day I was creating and documenting one of my library and a situation came by where I needed a custom exception. It was not clear at first but it became evident when I started the implementation of my interfaces. Why at the implementation stage? Let me show you a case where a custom exception would make sense, and why you would discover it at implementation time. Let's suppose we have an interface and two subclasses like:

both XmlReader and FlatFileReader are deriving from ireader

Let's say IReader is a general interface to read some data from a file of an unknown format. You're designing your interface which has only one method:

public interface IReader
{
    void Load(string uri);
}

Then, because you're a conscientious programmer, you decide to document the interface (for the subclasses to know when they should throw an exception while implementing the interface methods):

public interface IReader
{
    /// <summary>
    /// Loads and parse a file
    /// </summary>
    /// <exception name="ArgumentNullException">uri is null</exception>
    /// <exception name="ArgumentException">uri is not a valid uri</exception>
    /// <exception name="FileNotFoundException">file at uri is not found</exception>
    void Load(string uri);
}

You would think that's pretty it. But when imlementing the XmlReader subclass, a problem become evident. Loading an XML file can throw the following exceptions: SecurityException, FileNotFoundException or a XmlException whether or not the client have the rigths to make the loading call, whether or not the XML file exists or finally if the file is not a valid XML file. We can't let the Load method throw these because they're not related to any of the others implementations or more importantly to the interface. Remember, the client will most likely have an instance of the IReader interface so he won't know anthing about the way he's reading the file. So how can he do efficient exception handling?

A general rule is to avoid creating custom exceptions when we can use a system one. Another rule is to keep the custom exceptions count to a minimum. I recently learned those rules when I was having a talk on StackOverflow regarding the documentation of interfaces, which got me thinking of writing this article. Anyways back to our case, following those rules I think it's a good idea to create a custom exception to cover everything that could happen on the subclasses in a homogeneous way.

Instead of creating a ReaderSecurityException and a ReaderFileNotFoundException and such, let's just create a single exception ReaderException. The ReaderException will have a public member Reason to further explain the cause of the exception, eliminating the need for multiple exceptions.

Our first custom exception


public enum eReason
{
    CouldNotAccess,
    ParseError
    // [...]
}

public class ReaderException
{
    public eReason Reason { get; private set; }
}

Before completing the implementation of our exception, let's update the IReader interface documentation. We can now be generic enough to cover all implementations thanks to our custom exception:

public interface IReader
{
    /// <summary>
    /// Loads and parse a file
    /// </summary>
    /// <exception name="ArgumentNullException">uri is null</exception>
    /// <exception name="ArgumentException">uri is not a valid uri</exception>
    /// <exception name="FileNotFoundException">file at uri is not found</exception>
    /// <exception name="ReaderException">
    ///     <para>-or-</para>
    ///     the content of the file represented by uri is in an unknown format
    ///     <para>-or-</para>
    ///     the access to the file represented by uri is restricted
    /// </exception>
    void Load(string uri);
}

As you can see, we added an exception that can be thrown for two possible reasons. For example, when the file is not in an XML format, the XmlReader would throw a ReaderException with the reason being: Reason.ParseError. Alternatively, when the FlatFileReader encounter an invalid flat file format, it would throw the same. Pretty cool. Now let's see the proper way to create a custom exception.

Our final custom exception (properly serialized, of course)


I'm skipping the "how I did it" part on this one because I think the code is self explaining (the last constructor and the GetObjectData method are for serialization, you should serialize your extra members if you add any). Also I think you should focus your efforts on deciding whether or not to use a custom exception. A quick note however, never derive your custom exception from ApplicationException (see recap).

public enum eReason
{
    CouldNotAccess,
    ParseError
    // [...]
}

[Serializable]
public class ReaderException : Exception, ISerializable
{
    public eReason Reason { get; private set; }

    private ReaderException()
    {
    }

    public ReaderException(eReason reason)
            : base()
    {
        Reason = reason;
    }

    public ReaderException(eReason reason, string message)
            : base(message)
    {
        Reason = reason;
    }

    public ReaderException(eReason reason, string message, Exception inner)
            : base(message, inner)
    {
        Reason = reason;
    }

    protected ReaderException(SerializationInfo info, StreamingContext context)
    {
        if (info == null)
                throw new System.ArgumentNullException("info");

        Reason = (eReason)info.GetValue("Reason", typeof(eReason));
    }

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        if (info == null)
                throw new System.ArgumentNullException("info");

        info.AddValue("Reason", Reason, typeof(eReason));
    }
}

Throwing it


According to our examples above, we can now throw our custom exception like this in our XmlReader::Load method:

// [...]
try
{
    XDocument file = XDocument.Load(uri);
    // [...]
}
catch (SecurityException e)
{
    throw new ReaderException(eReason.CouldNotAccess, "Some pertinent comment", e);
}
catch (XmlException e)
{
    throw new ReaderException(eReason.ParseError, "Some pertinent comment", e);
}
// [...]

Catching it


Catching this exception is no different than any other exception, except that you now have your Reason property you can refer to. So according to the interface we should catch like this:

private void Test(IReader reader, string file)
{
    try
    {
        // We're assuming reader is not null
        reader.Load(file);
    }
    catch(ArgumentNullException e)
    {
        // [...]
    }
    catch(ArgumentException e)
    {
        // [...]
    }
    catch(FileNotFoundException e)
    {
        // [...]
    }
    catch(ReaderException e)
    {
        eReason r = e.Reason;
        // [...]
    }
}

That's it folks, we've made the loop! Hope you followed... if not, drop me a comment. But read on my summary and my conclusion to wrap things up.

Brief recap


  • Always try to use already available exceptions (ArgumentException, ArgumentNullException, InvalidOperationException, ArgumentOutOfRangeException, etc)
  • Never derive your exception from ApplicationException like I've seen it to much on other websites
  • Keep your custom exceptions count to a minimum
  • Use my template as a starting template :)
  • To back my claims on all of the above, refer to Best Practices for Handling Exceptions on msdn.

Conclusion


I think the article could have been much shorter. But I tried to elaborate a case to show you where and why it might be pertinent to create a custom exception. I didn't wanted to give you the code to properly create a custom exception right away.

I'm putting time and effort on this blog and having you here is my reward. Make me feel better and better everyday by spreading the love with the buttons below! Also, don't hesitate to leave a comment. Thanks for reading!

See ya

10 comments:

Chuck Durfee said...

In IE 6.0, all of the code samples are obscured by the scrollbars, by the way.

Hi, thanks for noticing.

I already opened a bug with the tool I'm using concerning that particular problem:
http://code.google.com/p/google-code-prettify/issues/detail?id=104

In the mean time, I'm really sorry.

Mike

I remove the function to "pretty print" the code for IE while the bug has not been fixed yet...

Sorry if it is a little bit hard to read for now.

Thanks

Chuck Durfee said...

Interesting article. One issue I see with this approach is that the consumer of the interface will need a copy of the eReason enumeration as well, won't they? Otherwise, you are essentially returning an HRESULT error code that the consumer has to interpret.

A string might be an alternative to using an enum, but it also might involve extra work if you are localizing exception output.

I was assuming a statically linked library (Add Reference DLL) so yes the enum will be visible to the client.

It will be in the namespace of the library which reinforce the meaning of the enum according to the it (Because there could be multiple eReason enums in a project)

Hope it clarifies things up a little.

Thanks for the comments, it will benefits others and it helps me straighten my articles!

Sincerely

John Wright said...

Great article!
I had been wondering about this as I'm documenting a .NET API that I didn't write. The API is definitely a bit rough around the edges in places and I don't believe this approach is used at all for exception handling and documentation. I'll be trying to work with the API developers to use this approach (or make the changes myself if authorized).
Thanks Mike!

Hi John,

Thanks for the comment, I hope you'll figure something out that will please both you and the team...

This is an article based on what I've learned personally and from others (notably on Stack Overflow http://stackoverflow.com/questions/1813902/documenting-interfaces-and-their-implementation) so there may be other good ways to do it...

Thanks again, I'm glad it helped you

John Wright said...

Yes that's how I found your article, that discussion is very helpful as well. I think the approach you've outlined here is very good at first glance. I can see the custom exception could reside in the same assembly (and namespace) as the interface.

Within your own code this is a good idea but could become overkill. I feel for a public facing API though, it is necessary to use an approach like this so the clients of the API can expect consistent and documented exceptions for all your interfaces.

The API I'm working with in particular has many interfaces that a client app would only use and not implement. So to provide them a robust/solid API, I need to have documented all the exceptions an interface can throw. This approach allows that and makes it the responsibility of interface implementations to handle their own exceptions and bubble up the appropriate custom (or standard) exception to the API level.

I never saw a solid argument in your article for not deriving your exceptions from ApplicationException. I don't disagree necessarily, I'm just curious why you say that.

I totally agree with you.

About ApplicationException, Yes I always should back my claims...

see:
"Note that Catching and Throwing Standard Exception Types has a guideline that states that you should not derive custom exceptions from ApplicationException."

http://msdn.microsoft.com/en-us/library/ms229064.aspx

This is very good article.
To learn more on custom exception class and live downloadable example visit this link.
http://jayeshsorathia.blogspot.com/2012/10/create-and-handle-custom-exception-in-net.html

©2009-2011 Mike Gleason jr Couturier All Rights Reserved