В каком порядке следует указывать несколько блоков catch в C#?

Ответ

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

Правильный порядок:

try
{
    // Код, который может вызвать исключение
    File.ReadAllText("missing.txt");
}
catch (FileNotFoundException ex) // Наиболее специфичное
{
    // Обрабатываем именно случай отсутствия файла
    Console.WriteLine($"Файл не найден: {ex.FileName}");
    // Можно создать файл по умолчанию
}
catch (IOException ex) // Более общее (FileNotFoundException наследуется от него)
{
    // Обрабатываем другие ошибки ввода-вывода (нет прав, диск полон и т.д.)
    Console.WriteLine($"Ошибка IO: {ex.Message}");
    _logger.LogWarning(ex, "IO error occurred");
}
catch (Exception ex) // Самое общее исключение
{
    // Перехватываем всё остальное (OutOfMemoryException, ArgumentException и пр.)
    // Обычно здесь только логирование и аварийное завершение операции
    _logger.LogError(ex, "Unexpected error");
    throw; // Пробрасываем дальше, если не можем восстановиться
}

Почему порядок важен: Выполняется первый блок catch, тип исключения которого совместим с выброшенным исключением. Если catch (Exception) будет первым, все последующие блоки станут недостижимым кодом.

Ответ 18+ 🔞

А, ну ты посмотри, какой умный нашелся! Решил все исключения в одну кучу свалить, да? Так, слушай сюда, дружок-пирожок, сейчас объясню, как не облажаться с этими catch блоками.

Представь, что исключения — это как бы твои родственники на семейном сборище. FileNotFoundException — это мелкий пиздюк, который потерял ключи. IOException — его уже более серьёзный отец, у которого проблемы посерьёзнее (диск сдох, прав нет). А Exception — это вообще ихний древний прадед, который всех породил и может появиться по любому поводу, от нехватки памяти до того, что ты тупо аргумент не тот передал.

Так вот, если ты этого деда-Exception поставишь встречать гостей первым, он всех загородит собой! Мелкий пиздюк прибежит — дед его уже обнял и в дом затащил. Отец приедет — та же история. Остальные ловильщики (catch) просто стоят и пальцем хуярят, потому что до них очередь никогда не дойдёт. Компилятор C# на это смотрит и такой: «Ну ты даёшь, мудила!» — и выдает ошибку CS0160. Недостижимый код, блядь!

Поэтому расставляй их по старшинству, от младшего к старшему. Сначала лови конкретную хрень, потом более общую, а уж самого всеядного деда — в самый конец. Он пусть ловит то, с чем остальные не справились.

try
{
    // Пытаемся что-то сделать
    File.ReadAllText("missing.txt");
}
catch (FileNotFoundException ex) // Ловим сначала мелкого (самое специфичное)
{
    // О, файлик потерялся! Чётко знаем, что делать.
    Console.WriteLine($"Бля, файл-то где? {ex.FileName}");
    // Можно, короче, новый создать
}
catch (IOException ex) // Потом ловим его папаню (более общее)
{
    // А тут уже другие проблемы с вводом-выводом: диск залочен, сеть отвалилась...
    Console.WriteLine($"Чёт IO глючит: {ex.Message}");
    _logger.LogWarning(ex, "IO error occurred");
}
catch (Exception ex) // И только в самом конце — деда на все случаи жизни
{
    // Ловим всё остальное, что летит из try-блока
    // Сюда обычно попадает какая-то неожиданная дичь
    _logger.LogError(ex, "Ёбаный насос! Ничего не понял, но очень интересно!");
    throw; // И обычно — кидаем эту проблему дальше, на уровень выше
}

Запомни раз и навсегда: от конкретного к общему. И тогда у тебя код будет не только компилироваться, но и выглядеть, как у адекватного человека, а не как у того, кто на catch (Exception) молится.