Thursday, December 16, 2010

Exception Template

Here's an Exception template that complies with all Code Analysis warnings and best practices:
Writing a custom exception couldn't be more straight forward.  However there are some things that are not obvious that can be useful to comply with.

The choice to write a custom exception usually follows the process of checking to see if an existing .NET framework exception adequately explains the issue with enough information.  DO NOT throw System.Exception or System.SystemException, that just reeks of rotten lazy coding.

If you're using the "All Rules" Code Analysis category in your project you'll be walked through a series of changes to comply.  These all make sense when you think about them. I'll walk through them below.  For the sake of expediency here's a template for a standard custom exception to save you the trouble of trying several times to resolve all warnings:


namespace MyNamespace
{
    using System;
    using System.Runtime.Serialization;

    /// <summary>
    /// A Custom exception ...
    /// </summary>
    [Serializable]
    public class CustomException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CustomException"/> class.        
        /// </summary>
        public CustomException()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomException"/> class.        
        /// </summary>
        /// <param name="message">The message.</param>
        public CustomException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomException"/> class.        
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="innerException">The inner exception.</param>
        public CustomException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomException"/> class.        
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> parameter is null. </exception>
        /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0). </exception>
        protected CustomException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
 

        /*
         * Use this when adding custom properties to an exception.
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("CustomProperty", CustomProperty);
        }
        */
        }
}


[Serializable]
This is good practice to allow exceptions to be saved in full when persisted, or sent over a communications channel. Although WCF requires that all top level exceptions be FaultExceptions, I believe you can use a serializable exception as an inner exception. Also consider implementing ISerializable.

Inherit from Exception.
There is no real benefit from having deep inheritance hierarchies of exceptions, and this is the recommendation of the Microsoft Framework Design Guidelines.  A good example of excellent use of Exception inheritance with good meaning are the System.IO.IOException types. This allows you to catch all IO related exceptions. Also the name should always be suffixed with "Exception" for clarity and consistency with other .NET framework exceptions.  Its better to have many specific custom exception types than a few overused types that dilute their meaning.  One last thing on this, don't bother deriving from ApplicationException, to quote Jeffrey Richter: "its a class that should not be part of the .NET framework. The original idea was that classes derived from SystemException would indicate CLR exceptions where non-CLR would derive from ApplicationException. However a lot of exception classes didn't follow this guideline...and it lost all meaning." Do not derive from any other exception other than Exception or SystemException (for more information see the Framework Design Guidelines chapter on exceptions).

Constructors.
Because constructors cannot be prescribed in a base-class the three exception constructors that are always expected to be present are the three shown above.  Your custom exception can be passed into .NET framework code and into other code that is consistent with the .NET framework. For this reason you should always have these three constructors available.

The last constructor is to ensure serialization is performed correctly. 

Don't go nuts on overloaded constructors rather prefer properties. 

Don't bother with error codes of any kind, this is what Exception types are for.

Another thing worth considering is using the System.Environment.FailFast method to ensure your process terminates quickly and as cleanly as possible.  In a no-win situation this is the best idea when there is an unhandled exception that leaves the application in an inconsistent state.

Don't be tempted to use custom exception types in "normal" program logic. Ie: catching a FileNotFoundException and then creating a file.  This will result in bad performing code.  Rather check and create (ie File.Exists method).

When making extensive use of logging, don't be caught out by thinking that logging means you don't need exceptions.  When writing reusable frameworks and toolkits, you cannot assume how someone will want to use your library. Log by all means, but throw an exception that the consumer can optionally catch and have the choice of attempting a recovery or failing the process.

No comments:

Post a Comment