Exception Handling in C#
Here, you will learn about exception handling in C# using try, catch, and finally blocks.
Exceptions in the application must be handled to prevent crashing of the program and unexpected result, log exceptions and continue with other functionalities. C# provides built-in support to handle the exception using try
, catch
& finally
blocks.
try { // put the code here that may raise exceptions } catch { // handle exception here } finally { // final cleanup code }
try block: Any suspected code that may raise exceptions should be put inside a try{ }
block. During the execution, if an exception occurs, the flow of the control jumps to the first matching catch
block.
catch block: The catch
block is an exception handler block where you can perform some action such as logging and auditing an exception. The catch
block takes a parameter of an exception type using which you can get the details of an exception.
finally block: The finally
block will always be executed whether an exception raised or not. Usually, a finally
block should be used to release resources, e.g., to close any stream or file objects that were opened in the try
block.
The following may throw an exception if you enter a non-numeric character.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enter a number: ");
var num = int.Parse(Console.ReadLine());
Console.WriteLine($"Squre of {num} is {num * num}");
}
}
To handle the possible exceptions in the above example, wrap the code inside a try
block and handle the exception in the catch
block, as shown below.
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("Enter a number: ");
var num = int.parse(Console.ReadLine());
Console.WriteLine($"Squre of {num} is {num * num}");
}
catch
{
Console.Write("Error occurred.");
}
finally
{
Console.Write("Re-try with a different number.");
}
}
}
In the above example, we wrapped this code inside a try
block. If an exception occurs inside a try
block, then the program will jump to the catch
block.
Inside a catch
block, we display a message to instruct the user about his mistake, and in the finally
block, we display a message about what to do after running a program.
try
block must be followed by catch
or finally
or both blocks. The try
block without a catch
or finally
block will give a compile-time error.
Ideally, a catch
block should include a parameter of a built-in or custom exception class to get an error detail. The following includes the Exception
type parameter that catches all types of exceptions.
try
catch
block
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("Enter a number: ");
var num = int.parse(Console.ReadLine());
Console.WriteLine($"Squre of {num} is {num * num}");
}
catch(Exception ex)
{
Console.Write("Error info:" + ex.Message);
}
finally
{
Console.Write("Re-try with a different number.");
}
}
}
Exception Filters
You can use multiple catch
blocks with the different exception type parameters. This is called exception filters. Exception filters are useful when you want to handle different types of exceptions in different ways.
class Program
{
static void Main(string[] args)
{
Console.Write("Please enter a number to divide 100: ");
try
{
int num = int.Parse(Console.ReadLine());
int result = 100 / num;
Console.WriteLine("100 / {0} = {1}", num, result);
}
catch(DivideByZeroException ex)
{
Console.Write("Cannot divide by zero. Please try again.");
}
catch(InvalidOperationException ex)
{
Console.Write("Invalid operation. Please try again.");
}
catch(FormatException ex)
{
Console.Write("Not a valid format. Please try again.");
}
catch(Exception ex)
{
Console.Write("Error occurred! Please try again.");
}
}
}
In the above example, we have specified multiple catch
blocks with different exception types. We can display an appropriate message to the user, depending upon the error, so the user does not repeat the same mistake again.
catch
blocks with the same exception type are not allowed. A catch
block with the base Exception type must be the last block.
Invalid catch Block
A parameterless catch
block and a catch
block with the Exception
parameter are not allowed in the same try
-catch statements, because they both do the same thing.
try
{
//code that may raise an exception
}
catch //cannot have both catch and catch(Exception ex)
{
Console.WriteLine("Exception occurred");
}
catch(Exception ex) //cannot have both catch and catch(Exception ex)
{
Console.WriteLine("Exception occurred");
}
Also, parameterless catch block catch{ }
or general catch block catch(Exception ex){ }
must be the last block. The compiler will give an error if you have other catch
blocks after a catch{ }
or catch(Exception ex)
block.
try
{
//code that may raise an exception
}
catch
{
// this catch block must be last block
}
catch (NullReferenceException nullEx)
{
Console.WriteLine(nullEx.Message);
}
catch (InvalidCastException inEx)
{
Console.WriteLine(inEx.Message);
}
finally Block
The finally
block is an optional block and should come after a try
or catch block. The finally
block will always be executed whether or not an exception occurred. The finally
block generally used for cleaning-up code e.g., disposing of unmanaged objects.
static void Main(string[] args)
{
FileInfo file = null;
try
{
Console.Write("Enter a file name to write: ");
string fileName = Console.ReadLine();
file = new FileInfo(fileName);
file.AppendText("Hello World!")
}
catch(Exception ex)
{
Console.WriteLine("Error occurred: {0}", ex.Message );
}
finally
{
// clean up file object here;
file = null;
}
}
finally
blocks are not allowed. Also, the finally
block cannot have the return, continue, or break keywords. It doesn't let control to leave the finally
block.
Nested try-catch
C# allows nested try-catch blocks. When using nested try-catch blocks, an exception will be caught in the first matching catch
block that follows the try
block where an exception occurred.
static void Main(string[] args)
{
var divider = 0;
try
{
try
{
var result = 100/divider;
}
catch
{
Console.WriteLine("Inner catch");
}
}
catch
{
Console.WriteLine("Outer catch");
}
}
An inner catch
block will be executed in the above example because it is the first catch
block that handles all exception types.
If there isn't an inner catch
block that matches with raised exception type, then the control will flow to the outer catch
block until it finds an appropriate exception filter. Consider the following example.
static void Main(string[] args)
{
var divider = 0;
try
{
try
{
var result = 100/divider;
}
catch(NullReferenceException ex)
{
Console.WriteLine("Inner catch");
}
}
catch
{
Console.WriteLine("Outer catch");
}
}
In the above example, an exception of type DivideByZeroException
will be raised. Because an inner catch
block handles only the NullReferenceTypeException
, it will be handle by an outer catch
block.