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.
- using System;
-
- namespace InnerExceptions {
- class Program {
- static void Main(string[] args) {
-
- Console.WriteLine("Enter first number");
-
-
- int FirstNumber = Convert.ToInt32(Console.ReadLine());
-
-
- Console.WriteLine("Enter second number");
- int SecondNumber = Convert.ToInt32(Console.ReadLine());
-
-
- int Result = FirstNumber / SecondNumber;
- Console.WriteLine(Result);
- }
- }
- }
Run the application
Pass the values for FirstNumber and SecondNumber.
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?
Our application throws an unhandled exception “System.FormatException”.
If a user enters 0 for the SecondNumber then we will get a dividedByZero 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.
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.
- try {
- Console.WriteLine("Enter first number");
- int FirstNumber = Convert.ToInt32(Console.ReadLine());
- Console.WriteLine("Enter second number");
- int SecondNumber = Convert.ToInt32(Console.ReadLine());
-
- int Result = FirstNumber / SecondNumber;
- Console.WriteLine(Result);
- }
In my E:\ drive I have a text file “InnerExceptionLog” in which we will log the exception.
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.
- catch (Exception ex) {
-
- string path = @"E:\Test\ InnerExceptionLog.txt";
-
-
- StreamWriter sw = new StreamWriter(path);
- }
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.
- sw.Write(ex.GetType().Name);
-
-
- sw.Close();
Provide a meaningful message to the front-end user.
- Console.WriteLine("Unkown error!!");
The entire try/catch block look like this:
- using System;
- using System.IO;
-
- namespace InnerExceptions {
- class Program {
- static void Main(string[] args) {
- try {
- Console.WriteLine("Enter first number");
- int FirstNumber = Convert.ToInt32(Console.ReadLine());
- Console.WriteLine("Enter second number");
- int SecondNumber = Convert.ToInt32(Console.ReadLine());
- int Result = FirstNumber / SecondNumber;
- Console.WriteLine(Result);
- } catch (Exception ex) {
- string path = @"E:\Test\ InnerExceptionLog.txt";
- StreamWriter sw = new StreamWriter(path);
- sw.Write(ex.GetType().Name);
- sw.Close();
- Console.WriteLine("Unkown error!!");
- }
- }
- }
- }
Save and run the application.
Enter some invalid values.
In the InnerExceptionLog file, the exception name will be logged.
If you want the message or stacktrace of the exception.
- sw.WriteLine(ex.Message);
- sw.WriteLine(ex.StackTrace);
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.
- catch (Exception ex) {
-
-
- string path = @"E:\Test\InnerExceptionLog1.txt";
- }
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.”.
- if (File.Exists(path)) {
- StreamWriter sw = new StreamWriter(path); sw.WriteLine(ex.GetType().Name);
- sw.WriteLine(ex.Message);
- sw.WriteLine(ex.StackTrace);
-
- sw.Close();
- }
- else
- {
- throw new FileNotFoundException("File not found",ex);
-
- }
-
- 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.
Whereas the
FileNotFoundException is our current exception.
Now let's run this program and look at what happens.
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.
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.
- using System;
- using System.IO;
-
- namespace InnerExceptions {
- class Program {
- static void Main(string[] args) {
- try {
- try {
- Console.WriteLine("Enter first number");
- int FirstNumber = Convert.ToInt32(Console.ReadLine());
- Console.WriteLine("Enter second number");
- int SecondNumber = Convert.ToInt32(Console.ReadLine());
- int Result = FirstNumber / SecondNumber;
- Console.WriteLine(Result);
- } catch (Exception ex) {
- string path = @"E:\Test\InnerExceptionLog1.txt";
- if (File.Exists(path)) {
- StreamWriter sw = new StreamWriter(path);
- sw.WriteLine(ex.GetType().Name);
- sw.WriteLine(ex.Message);
- sw.WriteLine(ex.StackTrace);
- sw.Close();
- } else {
- throw new FileNotFoundException("File not found", ex);
- }
- }
- } catch (Exception exx) {
- Console.WriteLine("Exception occurred in the catch block =>"+exx.GetType().Name);
-
- Console.WriteLine("Exception occurred in the catch block =>"+exx.InnerException.GetType().Name);
- }
- }
- }
- }
Run the applicationImportant noteThe 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.