Обработка ошибок в C#: try/catch, finally и лучшие практики

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Обработка ошибок в C#: try/catch и не только

Обработка ошибок — одна из ключевых составляющих надежного программного обеспечения. В C# для этой цели используется механизм исключений (exceptions). Грамотное применение конструкций try, catch и finally позволяет не только избежать аварийного завершения программы, но и сделать код более читаемым и предсказуемым. В этой статье мы разберем основы обработки исключений, лучшие практики и продвинутые возможности, которые предоставляет платформа .NET.

1. Базовый блок try/catch/finally

Основной инструмент обработки ошибок в C# — это блок try-catch-finally. Его синтаксис интуитивно понятен:

  • try — содержит код, который может выбросить исключение.
  • catch — перехватывает и обрабатывает исключение.
  • finally — выполняется всегда, независимо от того, было ли исключение. Используется для освобождения ресурсов (закрытие файлов, соединений с БД).

Рассмотрим простой пример — деление на ноль:

using System;

class Program
{
    static void Main()
    {
        try
        {
            int a = 10;
            int b = 0;
            int result = a / b; // Здесь возникнет исключение
            Console.WriteLine($"Результат: {result}");
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Ошибка: {ex.Message}");
        }
        finally
        {
            Console.WriteLine("Блок finally выполнен.");
        }
    }
}

Вывод программы:

Ошибка: Попытка деления на нуль.
Блок finally выполнен.

Обратите внимание: если не указать конкретный тип исключения, блок catch перехватит все исключения. Это может быть удобно, но часто считается плохой практикой, так как скрывает реальные проблемы.

2. Типы исключений и их иерархия

В C# все исключения наследуются от базового класса System.Exception. Наиболее часто используемые:

  • SystemException — базовый класс для системных исключений.
  • NullReferenceException — обращение к объекту, который равен null.
  • IndexOutOfRangeException — выход за границы массива.
  • InvalidOperationException — вызов метода в недопустимом состоянии.
  • ArgumentException и его наследники (ArgumentNullException, ArgumentOutOfRangeException) — неверные аргументы метода.

Вы можете перехватывать несколько типов исключений в одном блоке catch (начиная с C# 6):

try
{
    // код
}
catch (ArgumentNullException ex) when (ex.ParamName == "userId")
{
    Console.WriteLine("Параметр userId не должен быть null.");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"Некорректная операция: {ex.Message}");
}
catch (Exception ex) when (Log(ex, "Неизвестная ошибка"))
{
    // Фильтр исключений — блок выполняется, только если условие истинно
    throw; // Пробрасываем исключение дальше
}

Фильтры исключений (ключевое слово when) позволяют более гибко управлять логикой обработки, не теряя стек вызовов.

3. Генерация собственных исключений

Иногда стандартных исключений недостаточно. Вы можете создать свой класс исключения, унаследовав его от 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;
    }
}

Теперь вы можете использовать это исключение в своем коде:

class Program
{
    static void ValidateAge(int age)
    {
        if (age < 0 || age > 150)
        {
            throw new InvalidAgeException(age, "Возраст должен быть от 0 до 150 лет.");
        }
    }

    static void Main()
    {
        try
        {
            ValidateAge(-5);
        }
        catch (InvalidAgeException ex)
        {
            Console.WriteLine($"Ошибка: {ex.Message}. Возраст: {ex.Age}");
        }
    }
}

Собственные исключения делают код самодокументируемым и упрощают отладку.

4. Лучшие практики обработки исключений

Чтобы ваш код на C# был надежным и поддерживаемым, следуйте этим рекомендациям:

  • Не используйте исключения для управления потоком выполнения. Исключения — это механизм для обработки исключительных ситуаций, а не замена условным операторам.
  • Перехватывайте конкретные типы исключений. Избегайте пустого catch (Exception ex) без логирования.
  • Используйте блок finally для освобождения ресурсов. Если ваш класс реализует IDisposable, используйте конструкцию using — она автоматически вызывает Dispose() даже при исключении.
  • Не скрывайте исключения. Если вы перехватываете исключение, но не можете его обработать, пробросьте его дальше с помощью throw; (без указания переменной). Это сохраняет стек вызовов.
  • Логируйте исключения. Используйте такие библиотеки, как Serilog, NLog или встроенный ILogger из Microsoft.Extensions.Logging.
  • Не выбрасывайте Exception напрямую. Создавайте более конкретные типы исключений.

Пример правильного использования using:

using (var reader = new StreamReader("file.txt"))
{
    string content = reader.ReadToEnd();
    Console.WriteLine(content);
} // reader.Dispose() будет вызван автоматически, даже если произойдет исключение

5. Продвинутые возможности: глобальная обработка и Task

В приложениях .NET Core/.NET 5+ можно настроить глобальный обработчик исключений для всего приложения. Например, в ASP.NET Core это делается через middleware:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler(errorApp =>
    {
        errorApp.Run(async context =>
        {
            context.Response.StatusCode = 500;
            await context                

Блоги

Рекомендаций по книгам