An exception, in any programming language, is the error that happens during the program execution. It is also called a runtime error. There are basically two types of errors that we can have in our program. One is compile-time error and another is runtime error which is called the exception. The compile-time error happens during the program compilation and is basically due to wrong programming syntax or any other issue that stops compiler to a successful compilation of the program. On the other hand, the runtime error or the exception happens when the program is running. This means that the program compilation was successful, however, due to some error that was not known at the compile time an exception occurred in the program. For example, you are taking two numbers for division and the denominator somehow happened to be zero which makes the program to terminate abnormally.
Talking about the exception in C# language, the "System.Exception" class is the base class of all the exceptions defined by the C# programming language like the one we talked about earlier which was the "DivideByZeroException" exception. Even if there is an unknown exception which C# doesn't know, the "System.Exception" class is designed to handle that as well.
The "try-catch" block
The basic fundamental thing to handle the exception is to first try executing the block of code and if there is an exception then capture it. In C#, the try-catch block is used for this purpose. Under the "try" block, we usually write that part of the code in which there is a possibility of having an exception. You can understand this as that the try block tries to execute the block of code under it. The "catch" block, on the other hand, is used to capture the error that was occurred in the try block. The catch block optionally takes a parameter of type Exception or type of classes that inherits the Exception class.
So, let's see this with an example.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Try-Catch");
Console.WriteLine();
var i = 20;
var j = 0;
try
{
var k = i / j;
}
catch(Exception ex)
{
Console.WriteLine("Exception - " + ex.Message);
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
In the above program, we can see a try block where we are trying to divide the variable "i" with variable "j". Since the value of "j" at this point in time is 0, hence division will not be possible and an exception will be thrown. The catch block will capture the exception. In this program, we have declared an object ex of type Exception. The exception information will be captured in this object. In the catch block, we are simply writing the message of the exception.
* You might be wondering, what if we had not used the try-catch block. Well, in such cases the program will always crash and exit abnormally.
Handling specific exceptions
In the previous example, we have used the "Exception" class to capture the information about the exception that has occurred. The "Exception" class is the base class of all other exception classes. When we use the "Exception" class, any kind of exception will be captured by this class. However, if we need to capture specific exceptions, we need to use specific exception classes that are derived by the "Exception" class.
Let's revisit the previous example. This specific exception can be captured by the example class "DivideByZeroException" class that is provided by the .Net framework. So let's modify our previous example and use this specific exception class.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Specific-Try-Catch");
Console.WriteLine();
var i = 20;
var j = 0;
try
{
var k = i / j;
}
catch(DivideByZeroException ex)
{
Console.WriteLine("Exception - " + ex.Message);
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
n the above program, we have replaced the "Exception" class with the "DivideByZeroException" class. Since the type of exception that was thrown from the program was of the same type, we can successfully capture that exception in this specific exception class. Executing the above program will yield the same output.
Why handle specific exceptions?
Looking at the previous topic you would like to ask - "Why there is a need to writing specific exceptions? We can always capture any kind of exceptions using our "Exception" class."
The answer is - "We write specific exception classes to gracefully handle the exceptions. The specific exception classes carry additional information about the exception. Also, if we want to do some additional tasks whenever a specific exception occurs, we can write the code under the catch block capturing that specific exception."
The problem in handling specific exception
In the previous code example, if the type of exception were different from the "DivideByZeroException" exception, then the program would have been crashed. Let's understand this with the following example. We are now going to introduce one more exception case in our program of type "IndexOutOfRangeException".
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Specific-Try-Catch");
Console.WriteLine();
var i = 20;
var j = 0;
int[] ages = {10, 20};
try
{
Console.WriteLine(ages[2]);
var k = i / j;
}
catch(DivideByZeroException ex)
{
Console.WriteLine("Exception - " + ex.Message);
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
Trying the execute the above program will make it go crash. This is because the first line under the try block is trying to access the 3rd element from the "ages" array which does not exist. We have only two elements in the array. This will make the program throw an exception of type "IndexOutOfRangeException". Since we are only capturing the exception of the type "DivideByZeroException", the exception of type "IndexOutOfRangeException" becomes unhandled making the program to go crash.
This is where C# allows multiple catch blocks. Let's see this next.
Multiple catch blocks
We have seen previously that in case of multiple specific exceptions, we could not handle using a single catch block or clause. This is where C# allows us to write multiple catch blocks to cater to such cases. We will now modify the previous example and add one more catch block with an exception object of type "IndexOutOfRangeException".
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Multiple-Catch-Clauses");
Console.WriteLine();
var i = 20;
var j = 0;
int[] ages = {10, 20};
try
{
Console.WriteLine(ages[2]);
var k = i / j;
}
catch(DivideByZeroException ex)
{
Console.WriteLine("Exception - Can not divide by zero." + " [Message - " + ex.Message + "]");
}
catch(Exception ex)
{
Console.WriteLine("Exception -" + " [Message - " + ex.Message + "]");
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
In the above code, when the first line under try block ( Console.WriteLine(ages[2]); ) gets executed, it will throw an exception of type "IndexOutOfRangeException". Since we have only two exception classes "DivideByZeroException" and "Exception", the exception will be captured by the catch block which is "catch(Exception ex)". The is because the "Exception" class can capture any exception type.
Again, the problem here is - What if we want to capture the specific exception "IndexOutOfRangeException" as well? Well, we can add one more catch block that takes the argument of type "IndexOutOfRangeException".
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Multiple-Catch-Clauses");
Console.WriteLine();
var i = 20;
var j = 0;
int[] ages = {10, 20};
try
{
Console.WriteLine(ages[2]);
var k = i / j;
}
catch(DivideByZeroException ex)
{
Console.WriteLine("Exception - Can not divide by zero." + " [Message - " + ex.Message + "]");
}
catch(IndexOutOfRangeException ex)
{
Console.WriteLine("Exception - No element exists at this index position" + " [Message - " + ex.Message + "]");
}
catch(Exception ex)
{
Console.WriteLine("Exception -" + " [Message - " + ex.Message + "]");
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
Order of execution of catch blocks
There is an order in which the C# runtime will capture the exception to the specific catch block or clause. When an exception occurs, the C# runtime starts checking from the first catch block. If the exception type matches, then the control will go to that catch block, otherwise, the next catch block will be checked for the exception type. In this order only, the runtime will try to match the exception type and the first matching catch block will capture the exception.
If we write the catch block of "Exception" type in the very first place, then no other catch block will ever be able to capture the exception, even if it matches. This is because the type "Exception" can capture any exception. This is the reason why we always place the catch block of the "Exception" type at the very last place.
Throwing an exception
Till now, we were handing some of the well-known exceptions. So what if we want the exception to be thrown on a specific condition. Well, C# provides the "throw" keyword using which we can explicitly throw an exception to a program based on some condition. Let's see this with the help of an example.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Throwing Exception");
Console.WriteLine();
var width = 20;
var length = 15;
try
{
if(width > length)
{
throw new Exception("Width can not be greater than Length");
}
}
catch(Exception ex)
{
Console.WriteLine("Message - " + ex.Message);
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
// Output :
// Message - Width can not be greater than Length
In the above program, have a look at the try block. We are checking if the value of the variable "width" is greater than the value of the variable "length". If this the case then we are explicitly throwing an exception using the "throw" keyword. the "throw" keyword takes an object of type "Exception" class or any other class that is derived from the "Exception" class. Here, we are simply using the object of the "Exception" class and passing a custom message in the constructor of the "Exception" class. Now, when the code is executed, the exception will be thrown from the "try" block and the catch block will capture the exception object. You will see that the same message will be printed to the console.
The "finally" block
In all our previous examples, we did use the "try-catch" block to handle exceptions in our program. C# provides an additional optional clause "finally" that goes along with the "try-catch" block. which we call the "try-catch-finally" block. Let's first understand how the "finally" block works.
Consider a situation where we want some code to be executed irrespective of whether an exception has occurred or not. Now, you will say that we can wite that code after the last "catch" block and it will always be executed. The problem is there is no guarantee that the code we are writing after the last catch block will be executed. It may be because of the following two reasons:
- We are returning the method either from a try block or a catch block.
- There is no matching exception class in all catch blocks.
Hence, we need some provision that will guarantee us that specific code can be executed always irrespective of whatever happens inside the try block.
This is possible with the "finally" block. The "finally" block guarantees to execute the block of code written inside the "finally" block to be executed irrespective of whatever happens inside the "try" or the "catch" block. The "finally" block is written after the last "catch" block. So, let's see an example here.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Finally clause");
Console.WriteLine();
var i = 20;
var j = 0;
try
{
var k = i / j;
}
catch(DivideByZeroException ex)
{
Console.WriteLine("Exception - Can not divide by zero." + " [Message - " + ex.Message + "]");
}
catch(Exception ex)
{
Console.WriteLine("Message - " + ex.Message);
}
finally
{
Console.Write("This block will always execute");
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
/*
Output ->
Finally clause
Exception - Can not divide by zero. [Message - Attempted to divide by zero.]
This block will always execute
Press any key to continue...
*/
In the above program, we have added a "finally" block. This will always be executed. You can try with different scenarios like returning from try block or removing both the catch block. In any case, the "finally" block will be executed.
Exploring the "Exception" class
The "Exception" class is a class that is specifically designed to handle any exception that can occur in our program. The "Exception" class is the base class of all the other exception classes. In our previous examples, we have consumed the object of Exception class as "ex". Then we used its property "Message" to display the exception message. Along with this Message property, the "Exception" class holds many other important properties that can be used in one or more specific use cases. Let's see these properties.
Constructor | Description |
---|---|
Exception() | This is a default constructor. This comes with blank "Message" and "InnerException" properties. |
Exception(string message) | This constructor is used to set the "Message" property and the "InnerException" property will be blank. |
Exception(string message, Exception innerException) | This constructor is used to set both the the "Message" and the "InnerException" properties. |
Writing a custom exception class
We know that C# provides many specific exception classes for its known exception cases and we have used a couple of them as well that are "DivideByZeroException" and "IndexOutOfRangeException". These specific exceptions are derived from the base class "Exception".
If we want to write our own such specific exception class, the only thing we need to do is to write a class that derives from the base exception class "Exception". Please note that the default convention of writing specific exception class is the suffix the class name with the letter "Exception".
So, let's see this with an example. Here, we are going to write an exception class "AgeNotValidException". This will create an exception whenever we will try to assign age value to an employee that is below or above the specified age range.
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Custom exception class.");
Console.WriteLine();
try
{
var employee = new Employee("Alice", 17);
}
catch(AgeNotValidException ex)
{
Console.WriteLine("Age is not valid - " + ex.Message);
}
catch(Exception ex)
{
Console.WriteLine("Exception - " + ex.Message);
}
Console.Write("Press any key to continue...");
Console.ReadKey(true);
}
}
class AgeNotValidException: Exception
{
public AgeNotValidException() : base() { }
public AgeNotValidException(string message): base(message) { }
}
class Employee
{
string name= "";
int age = -1;
public Employee(string name, int age)
{
this.name = name;
if(age < 18 || age > 55)
{
throw new AgeNotValidException($"{age} is not a valid age to be an employee.");
}
else
{
this.age = age;
}
}
void Print() {
Console.WriteLine($"Name : {this.name} | Age : {this.age}");
}
}
Let's now understand the above program. First, we created our custom exception class "AgeNotValidException" by inheriting from the base "Exception" class. Then we have overridden its two constructors - one is default constructor and other with single parameter "message". After this, we have created an "Employee" class and in the constructor of the "Employee" class, we are checking the valid age range. If the age is not valid, we are throwing our custom exception using "throw new AgeNotValidException(..)".
Now in the main program, we are initializing an employee object under the try block. Since we have passed age as "17" in the constructor of the "Employee" class, the constructor will throw an exception of type "AgeNotValidException". This exception will be captured by the catch block of exception type "AgeNotValidException" and the message will be shown. It will yield the following output.
Custom exception class.
Exception - 17 is not a valid age to be an employee.
Press any key to continue...