Exception handling in C#

Exception handling is a crucial aspect of writing robust and fault-tolerant applications in C#. It ensures that your application can gracefully handle unexpected errors without crashing. In this blog, we’ll explore the fundamentals of exception handling in C#, best practices, and advanced techniques.

What is an Exception?

An exception is an unexpected event that occurs during the execution of a program, disrupting the normal flow. Exceptions can be caused by various reasons such as invalid user input, file not found, network failures, or division by zero.

The try-catch Block

C# provides the try-catch block to handle exceptions gracefully.

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

Multiple Catch Blocks

You can handle different types of exceptions using multiple catch blocks.

try
{
    string[] arr = new string[2];
    Console.WriteLine(arr[5]); // This will throw an IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine("Index out of range: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("General exception: " + ex.Message);
}

The finally Block

The finally block is used to execute code regardless of whether an exception occurs or not. It is often used for cleanup operations like closing file streams or database connections.

StreamReader reader = null;
try
{
    reader = new StreamReader("file.txt");
    Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("File not found: " + ex.Message);
}
finally
{
    reader?.Close(); // Ensures the file is closed
    Console.WriteLine("Cleanup complete.");
}

Throwing Exceptions

You can throw exceptions using the throw keyword.

void ValidateAge(int age)
{
    if (age < 18)
    {
        throw new ArgumentException("Age must be 18 or older.");
    }
    Console.WriteLine("Valid age.");
}

try
{
    ValidateAge(16);
}
catch (ArgumentException ex)
{
    Console.WriteLine("Exception: " + ex.Message);
}

Custom Exceptions

In some cases, you may need to create your own exception classes.

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

void CheckAge(int age)
{
    if (age < 18)
    {
        throw new InvalidAgeException("Age is below the required limit.");
    }
}

try
{
    CheckAge(16);
}
catch (InvalidAgeException ex)
{
    Console.WriteLine("Custom Exception: " + ex.Message);
}

Advanced Exception Handling Techniques

Using when with Catch Blocks

The when keyword allows you to filter exceptions based on conditions.

try
{
    int value = int.Parse("abc");
}
catch (FormatException ex) when (ex.Message.Contains("Input string was not in a correct format"))
{
    Console.WriteLine("Caught a format exception: " + ex.Message);
}

Exception Handling with Task and async/await

When working with asynchronous programming, handling exceptions properly is crucial.

async Task ReadFileAsync()
{
    try
    {
        using StreamReader reader = new StreamReader("file.txt");
        string content = await reader.ReadToEndAsync();
        Console.WriteLine(content);
    }
    catch (IOException ex)
    {
        Console.WriteLine("I/O Exception: " + ex.Message);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Unexpected Error: " + ex.Message);
    }
}

Global Exception Handling

For applications like ASP.NET Core or WPF, a global exception handler ensures unhandled exceptions do not crash the application.

ASP.NET Core Global Exception Handling

public class CustomExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public CustomExceptionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = 500;
            await context.Response.WriteAsync($"An error occurred: {ex.Message}");
        }
    }
}

Best Practices for Exception Handling

  1. Use specific exception types – Avoid catching generic Exception unless necessary.
  2. Avoid suppressing exceptions – Logging or handling them properly prevents hidden issues.
  3. Use finally for resource cleanup – Ensure proper disposal of resources like file streams and database connections.
  4. Do not use exceptions for control flow – Exceptions should be used for unexpected errors, not for normal logic.
  5. Log exceptions – Use logging frameworks (like Serilog, NLog, or log4net) to track exceptions.
  6. Use throw to preserve stack trace – Avoid throw ex;, as it resets the stack trace.
catch (Exception ex)
{
    Console.WriteLine("An error occurred.");
    throw; // Preserves stack trace
}

Conclusion

Exception handling is an essential skill for C# developers. By following best practices and leveraging C#’s robust exception-handling features, you can build more reliable and maintainable applications. Advanced techniques such as global exception handling and async exception handling further improve resilience.

Have any questions or best practices to share? Let’s discuss in the comments!

Leave a comment

Blog at WordPress.com.

Up ↑