Monday 21 September 2015

Custom Exceptions

There are numerous, exception classes provided check a list of existing Exceptions. If you can use one of these you probably should, however there may come a time when you need to create a custom exception class. To do so correctly you should following the following criteria
  1. Inherit from the Exception class: Custom exceptions should always inherit from the Exception class or one of its derived classes. This ensures that your custom exception can be caught and handled by catch blocks that are designed to handle exceptions.
  2. Provide three default constructors: Create three constructors that call their respective base implementations.
    • A parameter-less one
    • one that passes in a message 
    • one that passes in a message and an inner exception
  3. Provide a constructor that sets the message: It's a good practice to provide a constructor for your custom exception that sets the message. This allows you to provide a meaningful error message that describes the problem that caused the exception.
  4. Use a specific exception class for specific error: Create specific exception classes for specific errors, rather than using a general exception class for all errors. This allows you to handle specific errors in a more granular way and provides more information about what went wrong.
  5. Provide additional properties or methods: You can provide additional properties or methods on your custom exception class to provide more information about the error. This can be useful for logging or for displaying detailed error messages to the user.
  6. Use exception class name to reflect the error: You should use the exception class name to reflect the error, such as InvalidUserInputException, FileNotFoundException, etc.
  7. Provide inner exception: If you are wrapping an exception, you should provide the inner exception that caused the error, by using the constructor : base(message, innerException). This allows you to examine the original exception and understand the root cause of the problem.
  8. Document the exception: If you are creating a custom exception, it's important to document the exception, including when it might be thrown and what the possible causes of the exception are. This helps other developers understand how to use the exception and how to avoid throwing it.
Below is a contrived example of what a generic custom exception could look like


public class PersonException : Exception
{
    //Three Constructors are required by MS best practices

    //One empty
    public PersonException() { }

    //One that takes an error message associated with the Exception
    public PersonException(string msg) :
        base(msg) { }

    //one that takes the error message and and exception parameter
    //that wraps the original exception
    public PersonException(string msg, Exception inner) :
        base(msg, inner) { }

}


The above is the bare minimum you need to follow Microsoft best practices for custom exceptions. here's a full use leveraging our custom exception class.


using System.Text.RegularExpressions;

namespace pav.customException
{

    public class PersonException : Exception
    {
        //Three Constructors are required for MS best practices

        //One empty
        public PersonException() { }

        //One that takes an error message associated with the Exception
        public PersonException(string msg) :
            base(msg)
        { }

        //one that takes the error message and and exception parameter
        //that wraps the original exception
        public PersonException(string msg, Exception inner) :
            base(msg, inner)
        { }
    }
   
    class Person
    {
        private string _firstName;
        public string FirstName
        {
            get { return _firstName; }
            set
            {
                _firstName = CheckName(value);
            }
        }
        private string _lastName;

        public string LastName
        {
            get { return _lastName; }
            set { _lastName = CheckName(value); }
        }

        private string CheckName(string value)
        {
            if (Regex.IsMatch(value, @"^[a-zA-Z]{2,}$"))
                return value;
            throw new FormatException($"{value} is not a valid name");
        }
        public Person() {
            this._firstName = "";
            this._lastName = "";
         }

        public Person(string FirstName, string LastName) : this()
        {
            try
            {
                this.FirstName = FirstName;
                this.LastName = LastName;
            }
            catch (Exception ex)
            {
                throw new PersonException("A custom person exception", ex);
            }
        }
    }
   
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var p = new Person { FirstName = "Pawel", LastName = "Cicui2as" };
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            try
            {
                var p = new Person("Pawel", "CIucias2");
            }
            catch (PersonException ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.InnerException.Message);
            }
        }
    }
}


Remember that you really shouldn't need to create custom exceptions and should always try and leverage the standard c# ones, if you must create a custom exception, it's important to document it, including when it might be thrown and what the possible causes of the exception are.