Overview


Exceptions are used to indicate errors that 
occurs while running a program. Exception objects, which describe an error, are created and thrown with the throw keyword. After the exception is thrown the runtime then searches for the most compatible exception handler and provides a custom message (defined by the programmer) to the end-user. Many developers tend to use just the Exception class to handle exceptions in their code. However, to achieve maximum control of your code it's often advisable to use exception hierarchy to handle exceptions more efficiently.



Handling exception in hierarchy


There could be several levels of exceptions. Let us explore this concept with an example as follows:

using System;
  
namespace MathsException
{
   class Maths {
       static void Main(string[] args){
           int a = 5;
           int b = 0;
           var result = a / b;
       }
   }
}

The aforementioned code will throw an exception since dividing by zero is an exception. If we instead just use top level (generic) try-catch to handle exception then we will miss out on many fine tuning opportunities, for example:

using System;
   
namespace MathsException
{
   class Maths {
       static void Main(string[] args){
           int a = 5;
           int b = 0;
           try {
               var result = a / b;
           }
           catch (Exception ex) {
               Console.WriteLine("[Error]: " + ex.Message);
           }
       }
   }
}

On contrary, we can be more specific and use lower level exception handling to make the code more robust against such exceptions, for example:

using System;
     
namespace MathsException
{
   class Maths {
       static void Main(string[] args){
           int a = 5;
           int b = 0;
           try {
               var result = a / b;
           }
           catch (DivideByZeroException ex) {
               Console.WriteLine("[Error]: You can not divide by zero.");
            }
           catch (ArithmeticException ex) {
               Console.WriteLine("[Error]: An arithmetic error has occurred please check the values of the involved variables! More specific error: " + ex.Message);
           }
           catch (Exception ex) {
               Console.WriteLine("[Error]: " + ex.Message);
           }
       }
   }
}




Good practice 


It is a good practice to have the exception hierarchy from more specific (lowest level) to generic (top most level) such that we can make our code more robust against such exception, for example:


using System;
    
namespace MathsException
{
   class Maths {
       static void Main(string[] args){
           int a = 5;
           int b = 0;
           try {
               var result = a / b;
           }
           //We can fine tune the code to handle specific exception and make our code more robust as follows-->
           catch (DivideByZeroException ex) {
               Console.WriteLine("[Error]: You can not divide by zero. Please enter a non-zero integer value for the variable ->");
               b = Console.ReadLine();
               var result = a / b;
               Console.WriteLine("Updated result: " + result);
           }
           catch (ArithmeticException ex) {
               Console.WriteLine("[Error]: An arithmetic error has occurred please check the values of the involved variables! More specific error: " + ex.Message);
           }
           catch (Exception ex) {
               Console.WriteLine("[Error]: " + ex.Message);
           }
       }
   }
}

If we mix the order of the exceptions (DivideByZeroException, ArithmeticException, Exception in the aforementioned example) then Visual Studio will throw compile error, and hence, be careful to use the hierarchy wisely to make your code bullet proof.


See Also