Part 9: Exception Handling in C#

Image description

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.");
}

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}");
}

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}");
}

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}");
}

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}");
    }
}

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");
}

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.");
}
TipsBeginnersWeb DevelopmentDotnetCsharp
Avatar for Adrián Bailador

Written by Adrián Bailador

🚀 Full-Stack Dev 👨🏻‍💻 .NET Engineer 👾 Geek & Friki 💡 Talks about #dotnet, #csharp, #azure, #visualstudio and a little bit of #nextjs.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.