Exception Handling in C#

We have seen in the previous section that an exception is thrown by the CLR or program code if there is an error in the program. These exceptions need to be handle to prevent crashing of program. C# provides built-in support to handle the exception using try, catch & finally block.

Syntax:
try
{
    // code that may raise exceptions
}
catch(Exception ex)
{
    // handle exception
}
finally
{
    // final cleanup code
}

As per the above syntax, put the code in the try block that may raise an exception followed by a catch or finally block. Keep variable declarations out of the try block so that they can be accessed in the catch and finally blocks.

Let's see how to use try & catch block to handle the exception. Consider the following code which can raise an exception.

Example: Program throws an Exception
class Program
{
    static void Main(string[] args)
    {
        Console.Write("Enter Student Name: ");

        string studentName = Console.ReadLine();

        IList<String> studentList = FindAllStudentFromDatabase(studentName);

        Console.WriteLine("Total {0}: {1}", studentName, studentList.Count());

        Console.ReadKey();
    }

    private static IList<String> FindAllStudentFromDatabase(string studentName)
    {
        var studentList = // find all students with same name from the database 

        return studentList;
    }
}

In the above example, it display total number of students with the same name. Assume that FindAllStudentFromDatabase() method gets student list with the same name from the database.

The above example will work fine if at least one student exists for the specified name, otherwise it will raise NullReferenceException. We don't want to show exception message to the user and stop the execution. So, we need to handle exception using try catch block as shown below.

Example: Exception handling using try catch block
class Program
{
    static void Main(string[] args)
    {
        Console.Write("Enter Student Name: ");

        string studentName = Console.ReadLine();
        
        try
        {
            IList<String> studentList = FindAllStudentFromDatabase(studentName);

            Console.WriteLine("Total {0}: {1}", studentName, studentList.Count());
        }
        catch(Exception ex)
        {
            Console.Write("No Students exists for the specified name.");
        }

        Console.ReadKey();
    }

    private static IList<String> FindAllStudentFromDatabase(string studentName)
    {
        var studentList = // find all students with same name from the database 

        return studentList;
    }
}

As you can see in the above example, studentList.Count() can raise an exception if studentList is null. So wrap this code into try block. The try block simple tells the compiler to monitor the code for an exception. Exception raised within try block must be handled using catch block.

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

Catch block:

Exception raised within try block can be handled using the catch block as shown in the above example. Code in the catch block will only execute when an exception occurs.

A multiple catch block can also be specified with a different exception type is called exception filters. A multiple catch block is useful when you want to handle different exceptions in different ways.

Example: Multiple catch block
class Program
{
    static void Main(string[] args)
    {
        Console.Write("Please enter two numbers: ");
        
        try
        {
            int num1 = int.Parse(Console.ReadLine());
            int num2 = int.Parse(Console.ReadLine());

            int result = num1 / num2;

            Console.WriteLine("{0} / {1} = {2}", num1, num2, result);
        }
        catch(DivideByZeroException ex)
        {
            LogError(ex);
            Console.Write("Cannot divide by zero. Please try again.");
        }
        catch(InvalidOperationException ex)
        {
            LogError(ex);
            Console.Write("Not a valid number. Please try again.");
        }
        catch(FormatException  ex)
        {
            LogError(ex);
            Console.Write("Not a valid number. Please try again.");
        }

        Console.ReadKey();
    }

}

In the above example, we have specified a multiple catch block with different exception types, so that we can display the appropriate message to the user, depending upon the error and so the user does not repeat the same mistake again.

Note:
A multiple catch block with the same exception type is not allowed. It will give a compile time error.

Invalid catch Block

Parameterless catch block and a catch block with an 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 must come after a try or catch block. The finally block will always be executed whether or not an exception is thrown. The finally block is generally used for cleaning-up code e.g. for disposing an unmanaged objects etc.

Example: finally block
static void Main(string[] args)
{
    int zero = 0;    
    
    try
    {
        int result = 5/zero;  // this will throw an exception       
    }
    catch(Exception ex)
    {
        Console.WriteLine("Inside catch block. Exception: {0}", ex.Message );
    }
    finally
    {
        Console.WriteLine("Inside finally block.");
    }
}
Output:
Inside catch block. Exception: Attempted to divide by zero.
Inside finally
Note:
Multiple finally blocks are not allowed. Also, the finally block cannot have the return, continue, or break keywords. It doesn't allow control to leave the finally block.

Nested try-catch

C# allows nested try catch blocks. In the nested try catch block, an exception will be caught in the catch block that follows the try block where an exception occurred.

Example: Nested try-catch
static void Main(string[] args)
{
    Student std = null;
           
    try
    {
        try
        {
            std.StudentName = "";
        }
        catch
        {
            Console.WriteLine("Inner catch");
        }
    }
    catch
    {
        Console.WriteLine("Outer catch");
    }
}
  
Output:
Inner catch

If there isn't any inner catch block with appropriate exception type, then exception will flow to the outer catch block until it finds the appropriate exception filter. Consider the following example.

Example: Nested try-catch
static void Main(string[] args)
{
    Student std = null;
           
    try
    {
        try
        {
            // following throws NullReferenceException
            std.StudentName = "";
        }
        catch (InvalidOperationException innerEx)
        {
            Console.WriteLine("Inner catch");
        }
    }
    catch
    {
        Console.WriteLine("Outer catch");
    }
}
Output:
Outer catch

In the above example, std.StudentName will raise a NullReferenceException, but there is not any catch block that handles NullReferenceException or Exception type. So, it will be handled by the outer catch block.

Points to Remember :
  1. Use the try, catch and finally blocks to handle exceptions in C#.
  2. The try block must be followed by a catch or finally block or both.
  3. A multiple catch block is allowed with different exception filters. General catch{..} block must come last.
  4. catch{..} and catch(Exception ex){ } both cannot be used.
  5. The finally block must come after the try or catch block.
  6. The finally block will always execute irrespective of whether an exception occured or not.
  7. The finally block is appropriate place for disposing objects.
  8. The finally block cannot have a return or break because it isn't allow to leave the control.
  9. Nested try-catch blocks are allowed in C#.
  10. An Exception will be catched in the inner catch block if appropriate filter found, otherwise will be catched by outer catch block.

Learn how to raise an exception manually in the next section.