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

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

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

Начать курс

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

Любое приложение, даже написанное самым опытным разработчиком, может столкнуться с непредвиденными ситуациями: сбой сети, отсутствие файла на диске, деление на ноль или неверный ввод данных. В C# (.NET) для управления такими ситуациями используется механизм исключений (exceptions). В этой статье мы разберём всё, что нужно знать об обработке ошибок: от базовой конструкции try/catch до создания собственных типов исключений и современных best practices.

1. Основы: конструкция try/catch/finally

В C# обработка ошибок строится вокруг трёх ключевых блоков: try, catch и finally.

  • try — содержит код, который может выбросить исключение.
  • catch — перехватывает и обрабатывает конкретное исключение.
  • finally — выполняется всегда, независимо от того, было исключение или нет (обычно для освобождения ресурсов).
try
{
    int[] numbers = { 1, 2, 3 };
    Console.WriteLine(numbers[5]); // IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Ошибка: {ex.Message}");
}
finally
{
    Console.WriteLine("Блок finally выполняется всегда.");
}

Вы можете использовать несколько блоков catch для разных типов исключений. Обработка идёт сверху вниз, поэтому более конкретные исключения должны располагаться выше общих.

try
{
    // опасный код
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Деление на ноль!");
}
catch (Exception ex) // общий перехват
{
    Console.WriteLine($"Произошла ошибка: {ex.Message}");
}

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

Иногда вам нужно явно сообщить о некорректной ситуации. Для этого используется оператор throw. Вы можете выбросить как стандартное исключение, так и собственное.

public void ValidateAge(int age)
{
    if (age < 0 || age > 150)
    {
        throw new ArgumentOutOfRangeException(nameof(age), "Возраст должен быть от 0 до 150.");
    }
    Console.WriteLine("Возраст корректен.");
}

Также существует конструкция throw; (без аргументов) — она используется внутри блока catch для повторной генерации того же исключения. Это полезно, когда вы хотите залогировать ошибку, но не хотите её подавлять.

try
{
    // код
}
catch (Exception ex)
{
    Log.Error(ex); // запись в лог
    throw;         // повторная генерация — исключение летит дальше
}

3. Создание собственных исключений (Custom Exceptions)

Для бизнес-логики часто удобнее использовать свои типы исключений. Это делает код более читаемым и позволяет точно обрабатывать специфические ошибки.

Правила создания кастомного исключения:

  • Наследовать класс от Exception (или от ApplicationException, но лучше от Exception).
  • Реализовать три стандартных конструктора (без параметров, с сообщением, с сообщением и внутренним исключением).
  • Рекомендуется добавлять суффикс Exception к имени класса.
public class UserNotFoundException : Exception
{
    public UserNotFoundException() { }

    public UserNotFoundException(string message) : base(message) { }

    public UserNotFoundException(string message, Exception inner) : base(message, inner) { }
}

// Использование
throw new UserNotFoundException("Пользователь с ID 123 не найден.");

4. Best practices: как не навредить себе и проекту

Обработка ошибок — это не просто оборачивание всего в try/catch. Вот несколько золотых правил, которые стоит соблюдать в C# (.NET):

4.1. Не глотайте исключения

Пустой блок catch { } — это антипаттерн. Вы рискуете пропустить критическую ошибку и получить непредсказуемое поведение программы.

// ПЛОХО
try { DoSomething(); } catch { }

// ХОРОШО
try { DoSomething(); } catch (Exception ex) { Log.Error(ex); throw; }

4.2. Используйте конкретные типы исключений

Избегайте перехвата общего Exception, если вы ожидаете только FileNotFoundException. Это делает код более предсказуемым.

4.3. Освобождайте ресурсы через using или finally

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

using (var reader = new StreamReader("file.txt"))
{
    string content = reader.ReadToEnd();
    // Если здесь исключение — reader всё равно будет закрыт
}

4.4. Не используйте исключения для управления потоком

Исключения — это дорогая операция. Не стоит выбрасывать их для обычных проверок (например, вместо if).

// ПЛОХО
try { int.Parse(userInput); } catch { Console.WriteLine("Не число"); }

// ХОРОШО
if (int.TryParse(userInput, out int result)) { Console.WriteLine($"Число: {result}"); }

4.5. Логируйте ошибки с контекстом

Всегда старайтесь логировать не только сообщение, но и стек вызовов, а также данные, которые привели к ошибке. Это сэкономит часы дебага.

Заключение

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

Применяйте эти знания в своих проектах на .NET, и ваши приложения станут устойчивее к нештатным ситуациям. Успешного кодинга!

Блоги

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