Skip to main content

Command Palette

Search for a command to run...

Mastering C# Part 6 - Exception Handling

Everything about Exceptions in C#

Updated
5 min read
Mastering C# Part 6 - Exception Handling
N

Hi, I'm Nishant Banjade, a Software Engineer at Ellucian, committed to transforming education through technology. With expertise in developing CRM systems, plugins, workflows, APIs, and customized solutions for Higher Education, I bring a strong focus on innovation and efficiency. Currently, I'm honing my skills in full-stack software development, system design, data structures, algorithms, and Microsoft Dynamics 365.

I am a skilled software developer with expertise in C#/.NET, D365, JavaScript, TypeScript, ReactJS, SQL, and cloud technologies like AWS, along with experience in RabbitMQ, Docker, MUI, Tailwind, JIRA, and Git.

In C#, exception handling is a mechanism to handle runtime errors, which allows the program to deal with unexpected events (like file not found, database connection failure, etc.) gracefully without crashing. The goal is to catch exceptions, manage them, and provide meaningful feedback to the user or developer.

System Level Exception:

  • System exceptions are derived from the base class System.SystemException which in itself is a derived class of SystemException.

  • A System Exception occurs when a fatal or non-recoverable error is encountered, like a database crash, bound errors etc.

Application Level Exception:

  • Application-level exceptions are derived from the base class System.ApplicationException which is also a derived class of SystemException.

  • An Application-level exception occurs when a recoverable error is encountered, for example, the wrong type of input data, arithmetic exceptions etc.

Key Concepts of Exception Handling

  1. Exceptions: An exception is an object that represents an error condition in a program. It's derived from the base class System.Exception.

  2. Try-Catch-Finally: This is the main mechanism for handling exceptions in C#.

    • Try Block: Contains code that may throw exceptions.

    • Catch Block: Catches and handles the exception.

    • Finally Block: Optional block that contains code that runs regardless of whether an exception was thrown or not.

Exception Handling Structure: Try-Catch-Finally

The basic syntax for handling exceptions in C# is as follows:

try
{
    // Code that may throw an exception
}
catch (ExceptionType ex)
{
    // Code that runs if the exception occurs
}
finally
{
    // Code that runs regardless of whether an exception occurs or not
}

1. Try Block

The try block is where you place the code that might generate an exception. If an exception occurs within the try block, the runtime jumps to the catch block to handle the exception.

try
{
    int result = 10 / 0; // This will throw a DivideByZeroException
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Error: " + ex.Message); // Handle the exception
}

2. Catch Block

The catch block handles the exception thrown by the try block. It can be used to log the exception, show a message, or recover from the exception.

You can catch specific exception types or a generic exception type (base class of all exceptions).

Catching Specific Exceptions

try
{
    string[] arr = new string[3];
    arr[5] = "Hello"; // This will throw an IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine("Error: " + ex.Message); // Handle specific exception
}

Catching Multiple Exceptions

You can catch multiple types of exceptions in different catch blocks.

try
{
    int[] numbers = new int[3];
    Console.WriteLine(numbers[5]); // This will throw an IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine("Index Error: " + ex.Message);
}
catch (Exception ex) // Generic exception handler for other types
{
    Console.WriteLine("General Error: " + ex.Message);
}

Catching Generic Exception

You can catch all exceptions by using the base Exception class, but it's not recommended because it can hide other errors.

try
{
    int result = Convert.ToInt32("abc"); // This will throw a FormatException
}
catch (Exception ex)
{
    Console.WriteLine("Error: " + ex.Message); // Catch all exceptions
}

Using when Clause for Filtering Exceptions

You can filter exceptions by using the when clause to specify a condition under which an exception should be caught.

try
{
    int number = int.Parse("abc"); // This will throw FormatException
}
catch (FormatException ex) when (ex.Message.Contains("Input string"))
{
    Console.WriteLine("Format error caught: " + ex.Message);
}

3. Finally Block

The finally block contains code that will always run after the try and catch blocks, whether an exception was thrown or not. This is useful for cleanup operations like closing file streams, database connections, etc.

try
{
    Console.WriteLine("Attempting to open file...");
    // Code that may throw an exception (e.g., file operation)
}
catch (Exception ex)
{
    Console.WriteLine("Error: " + ex.Message); // Handle the exception
}
finally
{
    Console.WriteLine("Cleanup or resource release, regardless of success or failure.");
}

Custom Exceptions

You can create your own exceptions by inheriting from the System.Exception class or one of its derived classes.

Defining Custom Exception Class

public class InvalidAgeException : Exception
{
    public InvalidAgeException() { }

    public InvalidAgeException(string message)
        : base(message) { }

    public InvalidAgeException(string message, Exception inner)
        : base(message, inner) { }
}

Throwing Custom Exceptions

public void CheckAge(int age)
{
    if (age < 18)
    {
        throw new InvalidAgeException("Age cannot be less than 18.");
    }
    else
    {
        Console.WriteLine("Valid age.");
    }
}

Re-throwing Exceptions

Sometimes you might want to catch an exception, log it or handle it partially, and then re-throw it for further handling higher up the call stack.

try
{
    throw new InvalidOperationException("Something went wrong.");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine("Caught exception: " + ex.Message);
    throw; // Re-throwing the exception
}

Exception Hierarchy

In C#, all exceptions inherit from the base class System.Exception. The most commonly used exceptions are:

  • SystemException: Base class for predefined exceptions (e.g., NullReferenceException, IndexOutOfRangeException, InvalidOperationException).

  • ApplicationException: A base class for custom exceptions, although SystemException is recommended for most use cases.

Best Practices in Exception Handling

  1. Catch Specific Exceptions: Always try to catch the most specific exception types. Catching Exception is generally discouraged unless you need to handle all exceptions.

  2. Don’t Use Exception for Control Flow: Exceptions should be used for exceptional conditions, not for regular control flow in your program.

  3. Avoid Empty Catch Blocks: Don’t catch exceptions without doing anything with them. This can hide bugs and make debugging difficult.

  4. Use Finally for Cleanup: Use the finally block to release resources (e.g., close files, network connections) regardless of whether an exception was thrown.

  5. Use throw to Propagate Exceptions: After handling an exception, you can re-throw it to let higher layers handle it.

  6. Log Exceptions: It’s important to log exceptions to a file, event log, or monitoring system, especially for production applications.

Conclusion

Exception handling in C# is a powerful mechanism for managing runtime errors. By using try, catch, and finally blocks, you can ensure that your application responds gracefully to unexpected conditions. Always strive to handle exceptions in a meaningful way, log them, and use custom exceptions when necessary for better error handling in complex applications.