← Назад к вопросам

В какой последовательности нужно писать блоки catch?

1.0 Junior🔥 61 комментариев
#Основы C# и .NET

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Порядок блоков catch в C#

При обработке исключений в C# порядок блоков catch имеет фундаментальное значение, так как он определяет, какой обработчик исключения будет выполнен. Компилятор C# требует, чтобы блоки catch располагались в порядке от наиболее специфичных к наиболее общим типам исключений. Нарушение этого порядка приводит к ошибке компиляции.

Основной принцип: от специфичного к общему

try
{
    // Код, который может вызвать исключение
    int result = int.Parse("НЕ_ЧИСЛО");
}
catch (FormatException ex) // 1. Конкретное исключение
{
    Console.WriteLine($"Ошибка формата: {ex.Message}");
}
catch (ArithmeticException ex) // 2. Более общее арифметическое исключение
{
    Console.WriteLine($"Арифметическая ошибка: {ex.Message}");
}
catch (Exception ex) // 3. Самый общий тип исключений - всегда последний
{
    Console.WriteLine($"Общая ошибка: {ex.Message}");
}

Ключевые правила и рекомендации

  1. Иерархия наследования исключений:

    • ExceptionSystemExceptionArithmeticExceptionDivideByZeroException
    • ExceptionSystemExceptionArgumentExceptionArgumentNullException
    • Поэтому ArgumentNullException должен обрабатываться до ArgumentException, а тот — до Exception.
  2. Обязательное положение общего Exception:

    // НЕВЕРНО - ошибка компиляции:
    try { /* код */ }
    catch (Exception ex) { /* обработка */ }
    catch (FormatException ex) { /* этот блок никогда не выполнится */ }
    
    // ВЕРНО:
    try { /* код */ }
    catch (FormatException ex) { /* обработка конкретной ошибки */ }
    catch (Exception ex) { /* обработка всех остальных исключений */ }
    
  3. Фильтры исключений (when) не меняют порядок:

    try
    {
        // Код метода
    }
    catch (IOException ex) when (ex.Message.Contains("сетевая"))
    {
        // Обработка сетевых ошибок ввода-вывода
    }
    catch (IOException ex) // Более общий обработчик IOException
    {
        // Обработка остальных ошибок ввода-вывода
    }
    

Практические сценарии использования

Сценарий 1: Обработка бизнес-логики с кастомными исключениями

try
{
    ProcessOrder(order);
}
catch (InsufficientFundsException ex) // Пользовательское исключение
{
    Log.Warning($"Недостаточно средств: {ex.AccountId}");
    return PaymentResult.Failed("Недостаточно средств на счете");
}
catch (PaymentGatewayException ex) // Другое пользовательское исключение
{
    Log.Error($"Ошибка платежного шлюза: {ex.GatewayStatus}");
    return PaymentResult.Failed("Временная ошибка платежной системы");
}
catch (Exception ex) // Все непредвиденные исключения
{
    Log.Fatal(ex, "Критическая ошибка при обработке платежа");
    return PaymentResult.Failed("Внутренняя ошибка системы");
}

Сценарий 2: Поэтапное извлечение информации при обработке

try
{
    var data = DeserializeJson<T>(jsonString);
}
catch (JsonSerializationException ex)
{
    // Конкретная ошибка сериализации
    throw new DataContractException("Ошибка формата данных", ex);
}
catch (InvalidOperationException ex) when (ex.Source == "Newtonsoft.Json")
{
    // Ошибки, специфичные для JSON-библиотеки
    throw new DataContractException("Некорректная структура JSON", ex);
}
catch (Exception ex)
{
    // Все остальные исключения
    throw new DataContractException("Неизвестная ошибка при обработке данных", ex);
}

Важные нюансы

  • Блоки catch без указания типа (общий catch) устарели и не рекомендуются:

    catch // Устаревшая форма - избегайте!
    {
        // Нельзя получить информацию об исключении
    }
    
  • Не перехватывайте критические исключения:

    // НЕ ДЕЛАЙТЕ ТАК:
    catch (StackOverflowException ex) { /* Это исключение обычно не должно перехватываться */ }
    catch (OutOfMemoryException ex) { /* Требует особой обработки на уровне приложения */ }
    
  • Логирование и повторные выбросы:

    catch (SpecificException ex)
    {
        _logger.LogError(ex, "Контекст ошибки");
        throw; // Сохраняет оригинальный stack trace
        // throw ex; // НЕ ИСПОЛЬЗУЙТЕ - теряет stack trace!
    }
    

Выводы

Правильный порядок блоков catch — это не только требование компилятора, но и важная практика написания надежного кода. Он позволяет:

  • Точно обрабатывать специфичные ошибки
  • Предотвращать "затенение" обработчиков исключений
  • Создавать понятную иерархию обработки ошибок
  • Упрощать отладку и поддержку кода

Всегда начинайте с наиболее специфичных исключений, заканчивайте общим Exception, и помните, что порядок имеет значение как для компилятора, так и для логики вашего приложения.

В какой последовательности нужно писать блоки catch? | PrepBro