DEV Community

Cover image for Part 9: Exception Handling in C#
Adrián Bailador
Adrián Bailador

Posted on

Part 9: Exception Handling in C#

Exception Handling in C

Exception handling is a fundamental part of developing robust and error-resilient applications. In this article, we'll explore how to manage errors and exceptions in C# effectively, covering basic concepts, best practices, and advanced techniques.

1. Introduction to Exception Handling

In C#, exceptions provide a structured way to detect and handle runtime errors. Effective exception handling is crucial for creating professional and secure software, as it helps developers gracefully manage errors, ensure system stability, and maintain user trust. Instead of crashing the program, exceptions allow you to gracefully recover or provide informative feedback to the user.

Common Exceptions in C#:

  • ArgumentNullException: Thrown when a null argument is passed.
  • InvalidOperationException: Thrown when an operation is invalid for the object's state.
  • FileNotFoundException: Thrown when a specified file cannot be found.

Why Handle Exceptions?

  • To ensure the application remains stable.
  • To provide meaningful error messages to users.
  • To log issues for debugging purposes.

For more details, refer to the official C# documentation on exceptions.

2. Using try, catch, and finally

Basic Syntax:

The try block contains code that may throw an exception. The catch block handles the exception, and the finally block executes cleanup code, whether an exception occurred or not.

Example:

try
{
    int result = 10 / 0; // This will throw a DivideByZeroException
    Console.WriteLine(result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
finally
{
    Console.WriteLine("Execution completed.");
}
Enter fullscreen mode Exit fullscreen mode

Visual Representation:

Here’s a simplified flowchart of how try-catch-finally works:

  1. Execute try block.
  2. If no exception, skip catch and execute finally.
  3. If an exception occurs, execute the matching catch block and then finally.

3. Throwing Exceptions

You can throw exceptions manually using the throw keyword, which is useful for indicating errors in your custom logic.

Example:

void ValidateNumber(int number)
{
    if (number < 0)
    {
        throw new ArgumentOutOfRangeException("number", "Number must be non-negative.");
    }
}

try
{
    ValidateNumber(-1);
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine($"Validation failed: {ex.Message}");
}
Enter fullscreen mode Exit fullscreen mode

4. Creating Custom Exceptions

Custom exceptions allow you to define errors specific to your application domain.

Example:

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

try
{
    throw new CustomException("This is a custom exception.");
}
catch (CustomException ex)
{
    Console.WriteLine($"Caught: {ex.Message}");
}
Enter fullscreen mode Exit fullscreen mode

UML Diagram for Exception Hierarchy:

A simple UML diagram illustrating how custom exceptions derive from the base Exception class can help visualize the structure.


5. Best Practices for Exception Handling

  1. Use Specific Exceptions: Catch specific exceptions instead of a generic Exception.
  2. Avoid Silent Failures: Always log or handle the exception meaningfully.
  3. Do Not Overuse Exceptions: Use exceptions for exceptional scenarios, not for regular control flow.
  4. Use finally for Cleanup: Ensure resources like file streams or database connections are closed.

6. Advanced Exception Handling

Handling Multiple Exceptions:

You can use multiple catch blocks or filters to handle different types of exceptions.

Example:

try
{
    int result = int.Parse("NotANumber");
}
catch (FormatException ex)
{
    Console.WriteLine($"Format error: {ex.Message}");
}
catch (Exception ex) when (ex is OverflowException)
{
    Console.WriteLine($"Overflow error: {ex.Message}");
}
Enter fullscreen mode Exit fullscreen mode

Asynchronous Exception Handling:

When working with async and await, exceptions must be handled using try-catch around the awaited task.

Example:

async Task FetchDataAsync()
{
    try
    {
        await Task.Run(() => throw new InvalidOperationException("Invalid operation"));
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine($"Async error: {ex.Message}");
    }
}
Enter fullscreen mode Exit fullscreen mode

7. Tools for Logging Exceptions

Integrate logging libraries like Serilog or NLog to log exceptions for better monitoring and debugging. For more information, check out the official Serilog documentation or NLog's official site to explore their features and implementation details.

Example with Serilog:

using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

try
{
    throw new Exception("Test exception");
}
catch (Exception ex)
{
    Log.Error(ex, "An error occurred");
}
Enter fullscreen mode Exit fullscreen mode

8. File Operations Example

File Operations:

Write a program that reads a file. Use exception handling to manage scenarios where the file does not exist.

Example:

try
{
    string content = File.ReadAllText("nonexistentfile.txt");
    Console.WriteLine(content);
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"An error occurred: {ex.Message}");
}
finally
{
    Console.WriteLine("File operation attempt completed.");
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)