Exception Handling in C# with Examples – try-catch, throw, finally & Custom Exceptions

Exception Handling in C# with Examples – try-catch, throw, finally & Custom Exceptions
  1. Built-in Exception Classes in C#
  2. Exception Handling in C#
  3. throw keyword in C#
  4. Custom Exception Type in C#

Built-in Exception Classes in C#

Here you will learn about the built-in exception classes in C#.

C# .NET includes built-in exception classes for every possible error. The Exception class is the base class of all the exception classes.

The following is a hierarchy of exception classes in .NET:

Exception Classes in .NET

In the above figure, the Exception class is the base class of the SystemException and ApplicationException classes. The SystemException class is the base class for all the built-in exception classes in .NET Framework.

The ApplicationException was recommended to be base class for all your custom exceptions classes (The custom exeception class should be created if non of the system exception classes can be used and you need new exception class for business rule violations or for other application related errors). It was meant to differentiates between exceptions defined by applications versus exceptions defined by the system. However, Microsoft now recommends to derive custom exception classes from the Exception class rather than the ApplicationException class .

The following figure shows how the NullReferenceException is thrown in Visual Studio debug mode when you access a null object property at runtime.

NullReferenceException

Built-in Exception Classes

The following table lists important built-in exception classes in .NET.

Exception Class Description
ArgumentException Raised when a non-null argument that is passed to a method is invalid.
ArgumentNullException Raised when null argument is passed to a method.
ArgumentOutOfRangeException Raised when the value of an argument is outside the range of valid values.
DivideByZeroException Raised when an integer value is divide by zero.
FileNotFoundException Raised when a physical file does not exist at the specified location.
FormatException Raised when a value is not in an appropriate format to be converted from a string by a conversion method such as Parse.
IndexOutOfRangeException Raised when an array index is outside the lower or upper bounds of an array or collection.
InvalidOperationException Raised when a method call is invalid in an object's current state.
KeyNotFoundException Raised when the specified key for accessing a member in a collection is not exists.
NotSupportedException Raised when a method or operation is not supported.
NullReferenceException Raised when program access members of null object.
OverflowException Raised when an arithmetic, casting, or conversion operation results in an overflow.
OutOfMemoryException Raised when a program does not get enough memory to execute the code.
StackOverflowException Raised when a stack in memory overflows.
TimeoutException The time interval allotted to an operation has expired.

When an error occurs, either the application code or the default handler handles the exception.


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:
trycatchfinally

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 <code>try</code> <code>catch</code> 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
{ 
    //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.


C# - throw keyword

We have seen in the previous section how to handle exceptions which are automatically raised by CLR. Here, we will see how to raise an exception manually.

An exception can be raised manually by using the throw keyword. Any type of exceptions which is derived from Exception class can be raised using the throw keyword.

Example: throw an exception
static void Main(string[] args)
{
    Student std = null;

    try
    {
        PrintStudentName(std);
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.Message );
    }                      

    Console.ReadKey();
}

private static void PrintStudentName( Student std)
{
    if (std  == null)
        throw new NullReferenceException("Student object is null.");
        
    Console.WriteLine(std.StudentName);
}
Output:
Student object is null.

In the above example, PrintStudentName() method raises NullReferenceException if Student object is null.

Please notice that throw creates an object of any valid exception type using the new keyword. The throw keyword cannot be used with any other type which does not derive from the Exception class.


Re-throwing an Exception

You can also re-throw an exception from the catch block to pass on to the caller and let the caller handle it the way they want. The following example re-throws an exception.

Example: throw an exception
static void Main(string[] args)
{
    try
    {
        Method1();
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.StackTrace);
    }                      
}

static void Method1()
{
    try
    {
        Method2();
    }
    catch(Exception ex)
    {
        throw;
    } 
}

static void Method2()
{
    string str = null;
    try
    {
        Console.WriteLine(str[0]);
    }
    catch(Exception ex)
    {
        throw;
    } 
}

In the above example, an exception occurs in Method2(). The catch block simply throws that exception using only throw keyword (not throw e). This will be handled in catch block in Method1() where it again re-throw the same exception and finally it is being handled in the Main() method. The stack trace of this exception will give you the full detail of where exactly this exception occurred.

If you re-throw an exception using exception parameter then it will not preserve the original exception and creates a new exception. The following example demonstrates this.

Example: throw an exception
static void Main(string[] args)
{
    try
    {
        Method1();
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.StackTrace);
    }                      
}

static void Method1()
{
    try
    {
        Method2();
    }
    catch(Exception ex)
    {
        throw ex;
    } 
}

static void Method2()
{
    string str = null;
    try
    {
        Console.WriteLine(str[0]);
    }
    catch(Exception ex)
    {
        throw;
    } 
}

In the above example, exception caught in the Main() method will display stack trace from Method1 and Main method. It will not display Method1 in stack trace as we re-throw exception in Method1() using throw ex. So, never throw an exception using throw <exception parameter>.


Custom Exception Type in C#

C# includes the built-in exception types such as NullReferenceException, MemoryOverflowException, etc. However, you often like to raise an exception when the business rule of your application gets violated. So, for this, you can create a custom exception class by deriving the ApplicationException class.

The .Net framework includes ApplicationException class since .Net v1.0. It was designed to use as a base class for the custom exception class. However, Microsoft now recommends Exception class to create a custom exception class. You should not throw an ApplicationException exception in your code, and you should not catch an ApplicationException exception unless you intend to re-throw the original exception.

For example, create InvalidStudentNameException class in a school application, which does not allow any special character or numeric value in a name of any of the students.

Example: ApplicationException
class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
}

[Serializable]
class InvalidStudentNameException : Exception
{
    public InvalidStudentNameException() {  }

    public InvalidStudentNameException(string name)
        : base(String.Format("Invalid Student Name: {0}", name))
    {

    }
}

Now, you can raise InvalidStudentNameException in your program whenever the name contains special characters or numbers. Use the throw keyword to raise an exception.

Example: throw custom exception
class Program
{
    static void Main(string[] args)
    {
        Student newStudent = null;
          
        try
        {               
            newStudent = new Student();
            newStudent.StudentName = "James007";
            
            ValidateStudent(newStudent);
        }
        catch(InvalidStudentNameException ex)
        {
            Console.WriteLine(ex.Message );
        }
          

        Console.ReadKey();
    }

    private static void ValidateStudent(Student std)
    {
        Regex regex = new Regex("^[a-zA-Z]+$");

        if (!regex.IsMatch(std.StudentName))
             <b>throw new InvalidStudentNameException(std.StudentName);</b>
            
    }
}
Output:
Invalid Student Name: James000

Thus, you can create custom exception classes to differentiate from system exceptions.

Sandip Mhaske

I’m a software developer exploring the depths of .NET, AWS, Angular, React, and digital entrepreneurship. Here, I decode complex problems, share insightful solutions, and navigate the evolving landscape of tech and finance.

Post a Comment

Previous Post Next Post