Обработка ошибок в C#: try/catch, finally и продвинутые техники

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

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

Начать курс

Введение в обработку ошибок в C#

В любом приложении, особенно в сложных корпоративных системах на платформе .NET, ошибки неизбежны. Пользователь может ввести некорректные данные, файл может оказаться недоступным, а сетевое соединение — оборваться. Вместо того чтобы позволить программе аварийно завершиться, грамотный разработчик использует механизм обработки исключений.

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

1. Базовая конструкция: try, catch и finally

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

Синтаксис и пример

using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            Console.Write("Введите путь к файлу: ");
            string path = Console.ReadLine();
            string content = File.ReadAllText(path);
            Console.WriteLine("Содержимое файла:");
            Console.WriteLine(content);
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine($"Ошибка: Файл не найден. {ex.Message}");
        }
        catch (UnauthorizedAccessException ex)
        {
            Console.WriteLine($"Ошибка: Нет доступа к файлу. {ex.Message}");
        }
        catch (Exception ex) // Общий перехват всех остальных исключений
        {
            Console.WriteLine($"Неизвестная ошибка: {ex.Message}");
        }
        finally
        {
            Console.WriteLine("Блок finally выполнен. Освобождаем ресурсы (если нужно).");
        }
    }
}

Важные моменты:

  • Порядок блоков catch имеет значение: сначала нужно перехватывать более конкретные исключения (например, FileNotFoundException), затем — более общие (Exception). Если поставить общий блок первым, до более специфичных блоков дело никогда не дойдет.
  • Блок finally не обязателен. Если вам не нужно гарантированно освобождать ресурсы, его можно опустить.
  • Не перехватывайте Exception без необходимости. Если вы не знаете, как обработать ошибку, лучше позволить программе упасть — это поможет выявить проблему на этапе разработки.

2. Освобождение ресурсов с помощью using

Хотя finally — это мощный инструмент, писать его каждый раз для таких вещей, как закрытие файлов или подключений к базе данных, утомительно. Для объектов, которые реализуют интерфейс IDisposable, в C# существует оператор using. Он гарантирует вызов метода Dispose() при выходе из блока, даже если произошло исключение.

Сравнение подходов

Без using (ручное освобождение):

StreamReader reader = null;
try
{
    reader = new StreamReader("data.txt");
    string line = reader.ReadLine();
    // ... работа с reader
}
finally
{
    if (reader != null)
        reader.Dispose();
}

С использованием using:

using (StreamReader reader = new StreamReader("data.txt"))
{
    string line = reader.ReadLine();
    // ... работа с reader
}
// Dispose() будет вызван автоматически

Как видите, код стал короче и понятнее. Оператор using — это синтаксический сахар, который компилятор преобразует в блок try/finally. Его следует использовать всегда, когда вы работаете с такими ресурсами, как файлы, сетевые сокеты, соединения с БД или графические объекты GDI+.

3. Генерация исключений: оператор throw

Иногда вам нужно не только обрабатывать ошибки, но и создавать их. Это делается с помощью оператора throw. Вы можете выбросить как стандартное исключение (например, ArgumentException), так и собственное.

Проброс исключения (re-throw)

Частая ошибка — "проглатывание" исключения в блоке catch. Никогда не пишите пустой блок catch { } без обработки! Если вы не можете обработать ошибку на текущем уровне, пробросьте ее дальше. Правильный способ проброса:

try
{
    // Какой-то опасный код
}
catch (Exception ex)
{
    // Логируем ошибку
    LogError(ex);
    // Пробрасываем исключение, сохраняя стек вызовов
    throw;
}

Обратите внимание: используется throw; без указания объекта. Если написать throw ex;, вы сбросите стек вызовов, и будет гораздо сложнее понять, где именно произошла ошибка.

Создание собственного класса исключения

Для доменных ошибок (например, "Недостаточно средств на счете") лучше создавать свои классы. Это делает код более выразительным.

public class InsufficientFundsException : Exception
{
    public decimal Balance { get; }
    public decimal Amount { get; }

    public InsufficientFundsException(decimal balance, decimal amount)
        : base($"Недостаточно средств. Баланс: {balance:C}, требуется: {amount:C}")
    {
        Balance = balance;
        Amount = amount;
    }
}

// Использование
public void Withdraw(decimal amount)
{
    if (amount > _balance)
        throw new InsufficientFundsException(_balance, amount);
    _balance -= amount;
}

При создании собственного исключения следуйте рекомендациям Microsoft: делайте его сериализуемым (добавьте конструктор для десериализации), наследуйте от Exception и заканчивайте имя словом "Exception".

4. Лучшие практики и частые ошибки

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

Что НЕ нужно делать

  • Не используйте исключения для управления потоком выполнения. Исключения — это дорогостоящая операция. Если вы ожидаете, что пользователь может ввести неверные данные, лучше проверять их через if

Блоги

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