In-Depth Analysis Of Exception Handling In C#

Introduction

 
Welcome back all, I hope you all are keeping yourself in good health during this pandemic. 
 
In this article we are going to touch upon the following:
  • Exception vs. Error
  • How to handle an exception: Try, Catch, Finally & Throw
  • Filtering using multiple catches 
  • The Exception class
  • User-defined exception
  • Nested try-catch block
  • Catch with no arguments
  • Regulations while using exception handling in C#
  • Throw vs. Throw ex
First, we need to know what the exception is.  And  let's not get confused between an exception & the error.
 
In fact, let me give you an overview of how the error is different from an exception.
 
An Exception,
  • As per the definition, an exception is a problem/issue which occurs during the program's execution. which means it takes place during runtime.
  • Fair enough! But who defines exceptions in c#? well, there are various types of exceptions & they are defined by a system class Exception under System namespace.
A few types of exceptions:
  • ArithmeticException: Divide by 0
  • IndexOutOfRangeException: Try to access an index which does not exist
  • FileNotFoundException: If we try to locate a file in an invalid path, or the file's name does not match etc.
  • The best & my personal favourite: NullReferenceException: when the object is set to null and we try to access its properties or methods. Fortunately, we have a workaround for this, find out how we can avoid this exception in my article on null-pointer-design-pattern
The Error,
  • As per the definition, an error is a mistake made by a developer or designer, or it could be a fault in a system such as power failure or any hardware related failure. Such failures are out of a programmer's control.
  • The thing that differentiates the error from exception is that you can't handle an error, whereas exceptions are meant to be handled.
  • An error caused termination of the program.
A few types of errors,
  • Compile-time error - such as syntax error.
  • Run time error - it is nothing but an exception. 
  • Logical error - if the user gets unexpected output because of logical mistakes made by a programmer.
What happens if we don't handle an exception? Why is it so important to handle an exception?
 
There are 2 main reasons why we handle exceptions:
  • First, we don't want to close our program abnormally.
In-Depth Analysis Of Exception Handling In C#
  • Second, when the program closes with an exception, it exposes classes/ properties which are used in the project. This information is vital and vulnerable. Any malicious activities can be performed with this kind of information. SO BE CAREFUL!!!

How to handle an Exception

 
We can handle an exception with 4 beautiful keywords in C#.
  • Try
    It covers a block of statements. When you feel there are chances of getting an exception within a bunch of statements, then we have to encapsulate those statements inside a try block.
    • In the below example, let's try to read a file which might throw an exception if the file is not present in a specified directory. So I will have those statements inside a try block.
      1. try  
      2.  {  
      3.    StreamReader reader = new StreamReader("D://Myfile.txt");  
      4.  } 
  • Catch
    Above code will throw an exception & we need to catch that exception somewhere & that somewhere is Catch block,

    • Catch block only executes, if an exception has occurred.
      1. catch (Exception ex)  
      2. {  
      3.    Console.WriteLine("File was not found!");  
      4.    Console.WriteLine("This exception has occured while executing your program " +ex.Message);  

  • Finally
    Finally block executes regardless of exception occurrence. This is mostly used to free up any resources. In our example, we have opened a file which we need to close.

    • Note
      Always check for null condition, as we don't know if the file is opened or not.
      1. finally  
      2.   {  
      3.       if (reader != null)  
      4.      {  
      5.           reader.Close();  
      6.      }  
      7.   } 
    • Most of you might have a question. That is, I can release the resources after a catch block anyway. Why do I need finally?

      Answer
      Let's say you've encountered an exception within a try block, then control moves to a corresponding catch block. Now, what if you encountered another exception inside a catch block? Then statements below your catch block won't execute. But if you have those statements inside a finally they will surely execute.

      • So no matter where you get an exception or how many exceptions you get, finally will always execute.
  • Throw
    It is used when one wants to throw an exception manually.
Flow chart for a throw.
 
In-Depth Analysis Of Exception Handling In C#
  • The below code is throwing an ArithmeticException manually.

    Note
    The throw is mostly used with a user-defined exception. That we will learn down the road as we proceed further in the article.
    1. StreamReader reader = null;    
    2. try    
    3. {    
    4.     int divider = 0;    
    5.     int result = 100 / divider;    
    6.     throw new ArithmeticException();    
    7. }    
    8. catch (FileNotFoundException ex)    
    9. {    
    10.     Console.WriteLine("This exception has occured while executing your program " + ex.Message);    
    11. }    
    12. catch (ArithmeticException ex)    
    13. {    
    14.     Console.WriteLine("Sorry! But cannot divide by 0");    
    15.     Console.WriteLine("This exception has occured while executing your program " + ex.Message);    
    16. }    
Let's see a full-fledged example,
  1. class Program  
  2.    {  
  3.        static void Main(string[] args)  
  4.        {  
  5.            ReadFile();  
  6.        }  
  7.   
  8.        /// <summary>  
  9.        /// Reads the file.  
  10.        /// </summary>  
  11.        private static void ReadFile()  
  12.        {  
  13.            StreamReader reader = null;  
  14.            try  
  15.            {  
  16.                reader = new StreamReader("D://Myfile.txt");  
  17.            }  
  18.            catch (Exception ex)  
  19.            {  
  20.                Console.WriteLine("File was not found!");  
  21.                Console.WriteLine("This exception has occured while executing your program " + ex.Message);  
  22.            }  
  23.            finally  
  24.            {  
  25.                if (reader != null)  
  26.                {  
  27.                    reader.Close();  
  28.                }  
  29.            }  
  30.        }  
  31.    } 
As we have seen there is a single catch, but we can always have multiple catches.
 
Filtering using multiple Catches
  •  Our code has 2 possible exceptions.

    • FileNotFound
      If this exception occurs then "catch block with FileNotFoundException" will take care of it.

      If no exception occurs then code will execute the next line.

    • DivideByZero
      In the next lines, we are diving a  number by 0 which is not acceptable. So code will throw an exception which will be caught by "catch block with ArithmeticException".
If any other kind of exception occurs apart from these 2 then it will be caught by "catch block with an Exception". 
  1.         /// <summary>  
  2.         /// Reads the file.  
  3.         /// </summary>  
  4.         private static void ReadFile()  
  5.         {  
  6.             StreamReader reader = null;  
  7.             try  
  8.             {  
  9.                 //Reads a file  
  10.                 reader = new StreamReader("E://Myfile.txt");  
  11.                 //If no exception thrwon in above code, then we divide by 0  
  12.                 int divider = 0;  
  13.                 int result = 100 / divider;  
  14.             }  
  15.             catch (ArithmeticException ex)  
  16.             {  
  17.                 Console.WriteLine("Sorry! But cannot divide by 0");  
  18.                 Console.WriteLine("This exception has occured while executing your program " + ex.Message);  
  19.             }  
  20.             catch (FileNotFoundException ex)  
  21.             {  
  22.                 Console.WriteLine("This exception has occured while executing your program " + ex.Message);  
  23.             }  
  24.             catch (Exception ex)  
  25.             {  
  26.                 Console.WriteLine("This exception has occured while executing your program " + ex.Message);  
  27.             }  
  28.             finally  
  29.             {  
  30.                 if (reader != null)  
  31.                 {  
  32.                     reader.Close();  
  33.                 }  
  34.             }  
  35.         } 
Only one catch gets executed
  • Even though you can have multiple catches, still only one "catch block" gets executed. For example, in the above code when FileNotFoundException occurred, it looks for a catch block with FileNotFoundException exception where it gets handled. And then last catch block with Exception was simply ignored.
The parent of all exceptions: The class Exception
  • What if we move "catch block with an Exception" to the first position?

    • We will get a compile-time error. Saying the previous catch clause already catches all the exceptions. So the hierarchy is important, from a derived class to the base class.
In-Depth Analysis Of Exception Handling In C#
 
Why did this happen?
 
Because Exception is a parent class of all kinds of exceptions ever used in C#. 
 
In-Depth Analysis Of Exception Handling In C#
 
Wait a minute, if that is the case, If Exception is meant for inheritance. Then we can have our own exception. Same as FileNotFoundException or ArithmeticException.
 
User-defined Exception
 
Let's define a class which ensure the earth's shape is spheroid not flat.
  • First, create a class named as WrongShapeOfEarthException, Extend an Exception class. So as per IS-A relationship, our WrongShapeOfEarthException is officially an Exception.
  • Now create a parameterized constructor, which will take one string as a parameter. And this string is a nothing but a message of an exception.
  • You can also override the ToString() method of an object class. To print decorative information.
  1. public class WrongShapeOfEarthException : Exception  
  2.    {  
  3.        public WrongShapeOfEarthException(string errorMsg): base (errorMsg)  
  4.        {  
  5.   
  6.        }  
  7.        public override string ToString()  
  8.        {  
  9.            return "Earth's shape is Spheroid";  
  10.        }  
  11.    } 
Now let's call this exception,
 
In order to do that, we need to use a throw keyword.
  1.        /// <summary>  
  2.        /// Reads the file.  
  3.        /// </summary>  
  4.        private static void ReadFile()  
  5.        {  
  6.            try  
  7.            {  
  8.                string shapeOfTheEarth = "Flat";  
  9.                if(!string.IsNullOrWhiteSpace(shapeOfTheEarth) && shapeOfTheEarth != "Spheroid")  
  10.                {  
  11.                    throw new WrongShapeOfEarthException("Earth is not Flat");  
  12.                }  
  13.            }  
  14.            catch (WrongShapeOfEarthException ex)  
  15.            {  
  16.                Console.WriteLine("This exception has occured while executing your program " + Environment.NewLine);  
  17.                Console.WriteLine(ex.Message + Environment.NewLine);  
  18.                Console.WriteLine(ex.ToString());  
  19.            }  
  20.        } 
Let's check the output,
 
In-Depth Analysis Of Exception Handling In C#
 
Perfect!
 
Let's make it more complicated. Because WHY NOT? Right?
 
Nested try-catch block.
 
If we have nested try-catch blocks, then an exception will be caught by the first matching catch block. Let's understand this behavior with an example.
  1. using I_Cant_See_Sharp.Apple;  
  2. using I_Cant_See_Sharp.Entities;  
  3. using System;  
  4. using System.IO;  
  5.   
  6. namespace I_Cant_See_Sharp  
  7. {  
  8.   
  9.   
  10.     class Program  
  11.     {  
  12.         static void Main(string[] args)  
  13.         {  
  14.             ReadFile();  
  15.         }  
  16.   
  17.         /// <summary>  
  18.         /// Reads the file.  
  19.         /// </summary>  
  20.         private static void ReadFile()  
  21.         {  
  22.             try  
  23.             {  
  24.                 try  
  25.                 {  
  26.                     string shapeOfTheEarth = "Flat";  
  27.                     if (!string.IsNullOrWhiteSpace(shapeOfTheEarth) && shapeOfTheEarth != "Spheroid")  
  28.                     {  
  29.                         throw new WrongShapeOfEarthException("Earth is not Flat");  
  30.                     }  
  31.                 }  
  32.                 catch (Exception ex)  
  33.                 {  
  34.                     Console.WriteLine("Caught by Exception");  
  35.                 }  
  36.                  
  37.             }  
  38.             catch (WrongShapeOfEarthException ex)  
  39.             {  
  40.                 Console.WriteLine("This exception has occured while executing your program " + Environment.NewLine);  
  41.                 Console.WriteLine(ex.Message + Environment.NewLine);  
  42.                 Console.WriteLine(ex.ToString());  
  43.             }  
  44.         }  
  45.     }  
  46. }  
In-Depth Analysis Of Exception Handling In C#
 
first "catch block with Exception" got executed.
 
Let's replace Exception catch block with WrongShapeOfEarthException.
  1. using I_Cant_See_Sharp.Apple;  
  2. using I_Cant_See_Sharp.Entities;  
  3. using System;  
  4. using System.IO;  
  5.   
  6. namespace I_Cant_See_Sharp  
  7. {  
  8.   
  9.   
  10.     class Program  
  11.     {  
  12.         static void Main(string[] args)  
  13.         {  
  14.             ReadFile();  
  15.         }  
  16.   
  17.         /// <summary>  
  18.         /// Reads the file.  
  19.         /// </summary>  
  20.         private static void ReadFile()  
  21.         {  
  22.             try  
  23.             {  
  24.                 try  
  25.                 {  
  26.                     string shapeOfTheEarth = "Flat";  
  27.                     if (!string.IsNullOrWhiteSpace(shapeOfTheEarth) && shapeOfTheEarth != "Spheroid")  
  28.                     {  
  29.                         throw new WrongShapeOfEarthException("Earth is not Flat");  
  30.                     }  
  31.                 }  
  32.                 catch (WrongShapeOfEarthException ex)  
  33.                 {  
  34.                     Console.WriteLine("Caught by WrongShapeOfEarthException");  
  35.                     Console.WriteLine("This exception has occured while executing your program " + Environment.NewLine);  
  36.                     Console.WriteLine(ex.Message + Environment.NewLine);  
  37.                     Console.WriteLine(ex.ToString());  
  38.                 }  
  39.             }  
  40.               
  41.             catch (Exception ex)  
  42.             {  
  43.                 Console.WriteLine("Caught by Exception");  
  44.             }  
  45.         }  
  46.     }  
  47. }  
In-Depth Analysis Of Exception Handling In C#
 
Works as expected, First matching "catch block with a WrongShapeOfEarthException" got executed, ignoring the second  "catch block with an Exception".
 
Catch block with no arguments
  • We can also have catch block with absolutely 0 arguments.
  • This catch block will catch all exceptions, regardless of their type. 
  • However, you won’t be able to access any information about the exception.
  1. private static void ReadFile()  
  2.         {  
  3.             try  
  4.             {  
  5.                 int divider = 0;  
  6.                 int result = 6 / divider;  
  7.             }  
  8.             catch  
  9.             {  
  10.                 Console.WriteLine("Caught by Exception");  
  11.             }  
  12.         } 
But we can not have 2 catches, one with no arguments & other with argument.
 
The compiler will just give you a warning, it won't throw you a compile-time error. But better to avoid such code.
 
In-Depth Analysis Of Exception Handling In C#
 
However, as you can see, as per the sequence we have parameterized catch block first followed by the parameterless catch block.
 
If we interchange this sequence then the compiler will throw you an error instead of just a warning.
 
In-Depth Analysis Of Exception Handling In C#
 
Some important points we should know,
  • Try block at least needs one catch or one finally block. The presence of either of them is enough.

    • Else compiler will throw an error. "Either catch or finally is expected"

  • You can not have a catch or finally without a try block

    • The compiler will throw an error. "Try expected"

  • You need to have a proper sequence of try-catch-finally, else compiler will throw an exception if the flow is interrupted by other executable statements.

    • Comments are allowed, as comments are not executable statements.
    • If we write finally before the catch, the program won't execute.

      In-Depth Analysis Of Exception Handling In C#

  • Multiple catch blocks with the same exceptions are not allowed.
  • The parent exceptions should always be the last one.
  • Only one finally block is allowed, else syntax error will be thrown.

  • Finally Block

    • Finally block will always execute regardless of error's occurrence.
    • Finally block will execute even if the program returns a value before finally.

      e.g. after executing return statement inside a try or catch block => control will come to the finally for the execution => then it will move at the end of the method.

    • Finally block cannot have the return, continue, or break keywords.

      In-Depth Analysis Of Exception Handling In C#
Throw vs Throw ex
  • the throw: prints complete hierarchy from the origin. (origin: where exception was occurred and thrown)
  • throw ex: resets the stack trace - it breaks the hierarchy and makes itself an origin then prints errors from self.
Throw ex
 
Let's have a hierarchy,
 
Main method() => Planet (throw ex) => Continent (throw) (origin of an exception)
 
As per this heirarchy Continet will give the entire stacktrace as it is using throws; then Planet will break the stacktrace as it is using throw ex.
 
Let's see this in action,
  1. class Program  
  2.    {  
  3.        static void Main(string[] args)  
  4.        {  
  5.            try  
  6.            {  
  7.                Planet();  
  8.            }  
  9.            catch (Exception ex)  
  10.            {  
  11.                Console.Write(ex.StackTrace.ToString());  
  12.            }  
  13.        }  
  14.   
  15.          
  16.        private static void  Planet()  
  17.        {  
  18.            try  
  19.            {  
  20.                Continent();  
  21.            }  
  22.            catch (Exception ex)  
  23.            {  
  24.                //throw ex resets the stack trace Coming from Continent and propogates it to the caller   
  25.                throw ex;  
  26.            }  
  27.        }  
  28.   
  29.        private static void Continent()  
  30.        {  
  31.            try  
  32.            {  
  33.                throw new Exception("Inside Continent");  
  34.            }  
  35.            catch (Exception)  
  36.            {  
  37.                throw;  
  38.            }  
  39.        }  
  40.    } 
Let's see the output,
 
In-Depth Analysis Of Exception Handling In C#
 
As per the output Planet has broken the chain and makes himself an origin and printed stack trace from itself as it was using throw ex.
 
Throw
 
Same hierarchy but now Planet will use throw instead of throw ex
 
Main => Planet (throw) => Continent (throw) (origin of an exception)
 
As per this heirarchy Continet will pass entire stacktrace to the Planet as it is using throw => then Planet will add its own entry to the stacktrace and will pass updated stacktrace to the Main method() as it is also using throw => Main method will print an entire stacktrace.
 
Let's see this in action,
  1. class Program  
  2.   {  
  3.       static void Main(string[] args)  
  4.       {  
  5.           try  
  6.           {  
  7.               Planet();  
  8.           }  
  9.           catch (Exception ex)  
  10.           {  
  11.               Console.Write(ex.StackTrace.ToString());  
  12.           }  
  13.       }  
  14.   
  15.         
  16.       private static void  Planet()  
  17.       {  
  18.           try  
  19.           {  
  20.               Continent();  
  21.           }  
  22.           catch (Exception)  
  23.           {  
  24.               //throw ex resets the stack trace Coming from Continent and sends it to the caller   
  25.               throw;  
  26.           }  
  27.       }  
  28.   
  29.       private static void Continent()  
  30.       {  
  31.           try  
  32.           {  
  33.               throw new Exception("Inside Continent");  
  34.           }  
  35.           catch (Exception)  
  36.           {  
  37.               throw;  
  38.           }  
  39.       }  
  40.   } 
Let's see the output,
 
In-Depth Analysis Of Exception Handling In C#
 
As per the output Planet has not broken the chain this time and printed stack trace from the main origin of the exception => which is country.
 
Well, that's all for this article.
 
In conclusion, we learned:
  • How exceptions are handled in C#.
  • Difference between error and exception.
  • Different rules followed when we use exception handling.
  • How a user can define its own exception.
  • What scenarios to follow and not to follow.
I hope, this article was a little bit of everything for you.
 
If you have any further queries. comment below or connect with me @
Thank you all and as always Happy Coding!