Inner Exceptions in C#

In a previous article of the C# series, we learned about exceptions and how to handle exceptions using try/catch/finally blocks.

We have also learned that any exception that occurs are derived directly or indirectly from the System.Exception class and we also know that the base exception class exposes some helpful properties and methods like StackTrace, Message, GetType() and so on.

To learn more about exception handling, click here.

Demo

In this demo, we will create a new console application for which we will have two fields FirstNumber and LastNumber. We will divide the FirstNumber by LastNumber and will store the output in the third field Result. After getting the result we will print it on the console window.

If we get an exception, we will catch the exception and store it in a text file.

  1. using System;  
  2.   
  3. namespace InnerExceptions {  
  4.     class Program {  
  5.         static void Main(string[] args) {  
  6.             //prompt the user to enter the first number  
  7.             Console.WriteLine("Enter first number");  
  8.             //since, the return type of the ReadLine method is string  
  9.             //and we want the return type "int". So, we need to convert the ReadLine method  
  10.             int FirstNumber = Convert.ToInt32(Console.ReadLine());  
  11.   
  12.             //prompt the user to enter the second number  
  13.             Console.WriteLine("Enter second number");  
  14.             int SecondNumber = Convert.ToInt32(Console.ReadLine());  
  15.   
  16.             //divide  
  17.             int Result = FirstNumber / SecondNumber;  
  18.             Console.WriteLine(Result);  
  19.         }  
  20.     }  
  21. }  
Run the application

Pass the values for FirstNumber and SecondNumber.

Pass the values

The result is 5.

Now, what if a user passes a string value for the FirstNumber or what if a user passes the value 0 for the SecondNumber?

user pass some string

Our application throws an unhandled exception “System.FormatException”.

If a user enters 0 for the SecondNumber then we will get a dividedByZero exception.

exception

Or what if we pass an integer value greater than the maximum value that an integer field can hold?

We will get a System.OverFlow exception.

OverFlow exception

To handle these exceptions, we can use try/catch blocks.

We will place the error-prone code in the try block and if the try block throws an exception, the catch block will handle the exception.
  1. try {  
  2.     Console.WriteLine("Enter first number");  
  3.     int FirstNumber = Convert.ToInt32(Console.ReadLine());  
  4.     Console.WriteLine("Enter second number");  
  5.     int SecondNumber = Convert.ToInt32(Console.ReadLine());  
  6.   
  7.     int Result = FirstNumber / SecondNumber;  
  8.     Console.WriteLine(Result);  
  9. }  
In my E:\ drive I have a text file “InnerExceptionLog” in which we will log the exception.

InnerExceptionLog

To read from a file, we use the StreamReader class. To write in a file we use the StreamWriter class in the System.IO namespace.

 

  1. catch (Exception ex) {  
  2.    //path of the file  
  3.    string path = @"E:\Test\ InnerExceptionLog.txt";  
  4.   
  5.    //create new StreamWriter class object  
  6.    StreamWriter sw = new StreamWriter(path);//pass the path as an argument in the StreamWriter constructor  
  7. }
To get the type of the exception, we can invoke the GetType() method from the Exception class object and then we can invoke the Name property to get the name of the exception.

To write the exception details in a file, the StreamWriter provides a method “write” or “writeline” of which we can pass the ex.GetType().Name property.
  1. sw.Write(ex.GetType().Name);  
  2.   
  3. //close the stream  
  4. sw.Close();  
Provide a meaningful message to the front-end user.
  1. Console.WriteLine("Unkown error!!");  
The entire try/catch block look like this:
  1. using System;  
  2. using System.IO;  
  3.   
  4. namespace InnerExceptions {  
  5.     class Program {  
  6.         static void Main(string[] args) {  
  7.             try {  
  8.                 Console.WriteLine("Enter first number");  
  9.                 int FirstNumber = Convert.ToInt32(Console.ReadLine());  
  10.                 Console.WriteLine("Enter second number");  
  11.                 int SecondNumber = Convert.ToInt32(Console.ReadLine());  
  12.                 int Result = FirstNumber / SecondNumber;  
  13.                 Console.WriteLine(Result);  
  14.             } catch (Exception ex) {  
  15.                 string path = @"E:\Test\ InnerExceptionLog.txt";  
  16.                 StreamWriter sw = new StreamWriter(path);  
  17.                 sw.Write(ex.GetType().Name);  
  18.                 sw.Close();  
  19.                 Console.WriteLine("Unkown error!!");  
  20.             }  
  21.         }  
  22.     }  
  23. }  
Save and run the application.

Enter some invalid values.

invalid values

In the InnerExceptionLog file, the exception name will be logged.

exception name will be logged

If you want the message or stacktrace of the exception.
  1. sw.WriteLine(ex.Message);  
  2. sw.WriteLine(ex.StackTrace);  
stacktrace of the exception

So, we have handled the exceptions. But what if our catch block throws an exception?

What if the path we specified is invalid? What if I change the file name InnerExceptionLog to InnerExcpetionLog1?

We will get a FileNotFound exception.
  1. catch (Exception ex) {  
  2.   
  3.    //path of the text file  
  4.    string path = @"E:\Test\InnerExceptionLog1.txt";  
  5. }
So we need to check whether the specified path exists and for that we can use the File.Exists method. If the file name matches then all the exception details will be logged in that file else a new exception will be thrown using the throw keyword “FileNotFoundException.”. 
  1. if (File.Exists(path)) {  
  2.    StreamWriter sw = new StreamWriter(path); sw.WriteLine(ex.GetType().Name);  
  3.    sw.WriteLine(ex.Message);  
  4.    sw.WriteLine(ex.StackTrace);  
  5.    //close the stream  
  6.    sw.Close();  
  7. }
  8. else
  9. {  
  10.       throw new FileNotFoundException("File not found",ex);  
  11.    
  12. }  
  13.   
  14. throw new FileNotFoundException("File not found",ex);  
Pass the string message as the first parameter of the constructor and pass the exception object ex that holds the original exception details as the second parameter.

Note

Exception ex is our original exception. 
  1. catch (Exception ex) {  
  2. }  
Whereas the FileNotFoundException is our current exception.

Now let's run this program and look at what happens.

original exception

In the output we have two exceptions, the first one is the FileNotFoundException and the second one is the FormatException. But why is the exception order changed?

What I am trying to say here is that we passed an invalid input and an exception was thrown that was then passed to the catch block where ex, in other words the Exception variable ex, holds the exception details.

But then another exception was thrown because the file that we want the exception data to be logged in was missing.

Whenever there is more than one exception, the last occurred exception details is shown and the original exception loses its current state and is treated as an inner exception.

To retain the state and display the original exception, we pass its (Exception ex) ex object as a parameter in the newly occurred exception constructor.

So this is why the current exception is displayed first and the original exception is displayed after it.

exception construct

And if you want to handle both the current and the original exception, we need to pass the entire try/catch block into another try block and then catch the exception.
  1. using System;  
  2. using System.IO;  
  3.   
  4. namespace InnerExceptions {  
  5.     class Program {  
  6.         static void Main(string[] args) {  
  7.             try {  
  8.                 try {  
  9.                     Console.WriteLine("Enter first number");  
  10.                     int FirstNumber = Convert.ToInt32(Console.ReadLine());  
  11.                     Console.WriteLine("Enter second number");  
  12.                     int SecondNumber = Convert.ToInt32(Console.ReadLine());  
  13.                     int Result = FirstNumber / SecondNumber;  
  14.                     Console.WriteLine(Result);  
  15.                 } catch (Exception ex) {  
  16.                     string path = @"E:\Test\InnerExceptionLog1.txt";  
  17.                     if (File.Exists(path)) {  
  18.                         StreamWriter sw = new StreamWriter(path);              
  19.              sw.WriteLine(ex.GetType().Name);  
  20.                         sw.WriteLine(ex.Message);  
  21.                         sw.WriteLine(ex.StackTrace);  
  22.                         sw.Close();  
  23.                     } else {  
  24.                         throw new FileNotFoundException("File not found", ex);  
  25.                     }  
  26.                 }  
  27.             } catch (Exception exx) {  
  28.                 Console.WriteLine("Exception occurred in the catch block  =>"+exx.GetType().Name);  
  29.                 //to get the inner exception type and name, use the InnerException property present in the Exception class  
  30.                 Console.WriteLine("Exception occurred in the catch block  =>"+exx.InnerException.GetType().Name);  
  31.             }  
  32.         }  
  33.     }  
  34. }  
Run the application

Run the application

Important note

The InnerException property returns the exception instance that caused the current exception.

To retain the original exception, pass the exception instance as a parameter to the constructor of the current exception.