Error Handling in C#: try/catch and Beyond
Error handling is one of the key components of reliable software. In C#, the exception mechanism is used for this purpose. Proper use of try, catch, and finally constructs allows not only to avoid program crashes but also to make code more readable and predictable. In this article, we will cover the basics of exception handling, best practices, and advanced features provided by the .NET platform.
1. Basic try/catch/finally Block
The main tool for error handling in C# is the try-catch-finally block. Its syntax is intuitive:
- try — contains code that may throw an exception.
- catch — catches and handles the exception.
- finally — always executes, regardless of whether an exception occurred. Used for resource cleanup (closing files, database connections).
Let's look at a simple example — division by zero:
using System;
class Program{ static void Main() { try { int a = 10; int b = 0; int result = a / b; // An exception will occur here Console.WriteLine($"Result: {result}"); } catch (DivideByZeroException ex) { Console.WriteLine($"Error: {ex.Message}"); } finally { Console.WriteLine("Finally block executed."); } }}Program output:
Error: Attempted to divide by zero.Finally block executed.Note: if you don't specify a specific exception type, the catch block will catch all exceptions. This can be convenient, but is often considered bad practice as it hides real problems.
2. Exception Types and Their Hierarchy
In C#, all exceptions inherit from the base class System.Exception. The most commonly used ones are:
SystemException— base class for system exceptions.NullReferenceException— accessing an object that isnull.IndexOutOfRangeException— accessing an array index out of bounds.InvalidOperationException— calling a method in an invalid state.ArgumentExceptionand its descendants (ArgumentNullException,ArgumentOutOfRangeException) — invalid method arguments.
You can catch multiple exception types in a single catch block (starting from C# 6):
try{ // code}catch (ArgumentNullException ex) when (ex.ParamName == "userId"){ Console.WriteLine("The userId parameter must not be null.");}catch (InvalidOperationException ex){ Console.WriteLine($"Invalid operation: {ex.Message}");}catch (Exception ex) when (Log(ex, "Unknown error")){ // Exception filter — the block executes only if the condition is true throw; // Rethrow the exception}Exception filters (the when keyword) allow for more flexible control of handling logic without losing the call stack.
3. Creating Custom Exceptions
Sometimes standard exceptions are not enough. You can create your own exception class by inheriting from Exception:
public class InvalidAgeException : Exception{ public int Age { get; }
public InvalidAgeException(int age, string message) : base(message) { Age = age; }
public InvalidAgeException(int age, string message, Exception inner) : base(message, inner) { Age = age; }}Now