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.

Syntax:
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.

Example: C# Program
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.

Example: Exception handling using try-catch blocks
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.

Note:
A 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.

Example: Exception handling using 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.

Example: Exception Filters
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.

Note:
Multiple 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.

Example: Invalid catch
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.

Example: Invalid catch
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.

Example: finally Block
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;
    }
}
Note:
Multiple 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.

Example: Nested try-catch
static void Main(string[] args)
{
    var divider = 0;

    try
    {
        try
        {
            var result = 100/divider;
        }
        catch
        {
            Console.WriteLine("Inner catch");
        }
    }
    catch
    {
        Console.WriteLine("Outer catch");
    }
}
  
Output:
Inner 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.

Example: Nested try-catch
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");
    }
}
  
Output:
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.