Перейти к содержимому
Теория · Исключения · урок 2 из 9

try и catch

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

Аналогия неточна в одном. Дежурный, разобравшись с тревогой, возвращает людей на рабочие места. Программа после catch к месту ошибки не возвращается: строки блока try ниже этого места так и не выполнятся.

Как устроены try и catch

Код, который может выбросить исключение, ставят в блок try, а сразу за ним — блок catch:

C#
Console.WriteLine("Start"); // Start
try
{
    int n = int.Parse("ten");
    Console.WriteLine(n);
}
catch (FormatException ex)
{
    Console.WriteLine("Error: " + ex.Message); // Error: The input string 'ten' was not in a correct format.
}
Console.WriteLine("End"); // End

Давайте разберём программу по шагам:

  1. Start печатается как обычно.
  2. try по-английски значит «попробуй»: в его фигурных скобках стоит код, который может дать сбой.
  3. int.Parse("ten") выбрасывает FormatException. Остаток блока try пропускается: Console.WriteLine(n) не выполняется.
  4. catch значит «поймай». Блок catch (FormatException ex) ждёт исключения класса FormatException. Класс подходит, и блок печатает сообщение.
  5. После catch программа идёт дальше и печатает End. Исключение обработано, и программа не остановилась.

Блок catch называют обработчиком исключения. Когда обработчик выполняется, говорят, что он перехватил исключение.

Если заменить "ten" на "42", исключения не будет. Блок try выполнится целиком и напечатает 42, а catch не выполнится.

Переменная исключения

В скобках после catch стоят класс и имя переменной: FormatException ex. В ex лежит тот самый объект исключения, который создала .NET. Его свойство Message — сообщение, которое в отчёте шло после двоеточия.

Если объект не нужен, имя можно не писать: catch (FormatException). Блок по-прежнему ловит исключения этого класса.

Несколько блоков catch

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

C#
string text = "0";
try
{
    Console.WriteLine(100 / int.Parse(text));
}
catch (FormatException)
{
    Console.WriteLine("Not a number");
}
catch (Exception ex)
{
    Console.WriteLine("Error: " + ex.Message); // Error: Attempted to divide by zero.
}

Что напечатает программа, зависит от text:

  • "0" — деление на ноль выбрасывает DivideByZeroException. Первый блок ждёт FormatException и не подходит, второй печатает Error: Attempted to divide by zero.
  • "ten" — int.Parse выбрасывает FormatException, и первый блок печатает Not a number.
  • "8" — исключения нет, программа печатает 12, и оба блока пропускаются.

catch (Exception ex) подходит к любому исключению: все классы исключений производные от Exception. Поэтому блоки пишут от частного к общему: сначала конкретные классы, в конце Exception. Этот вопрос часто задают на собеседовании: в каком порядке писать блоки catch и почему.

Фильтр when

Иногда одного класса мало, чтобы решить, кто обработает исключение. Тогда после catch пишут условие when. Такое условие называют фильтром: блок выполняется, только если подходит класс и условие истинно.

C#
string text = "";
try
{
    int n = int.Parse(text);
    Console.WriteLine(n);
}
catch (FormatException) when (text == "")
{
    Console.WriteLine("Empty input"); // Empty input
}
catch (FormatException ex)
{
    Console.WriteLine(ex.Message);
}

В пустой строке нет числа, поэтому int.Parse выбрасывает FormatException. Условие text == "" истинно, и первый блок печатает Empty input. При text = "x" условие ложно, и первый блок пропускается, как будто его нет. Тогда второй блок ловит исключение и печатает The input string 'x' was not in a correct format.

Частая ошибка

Общий блок поставили выше конкретного:

C#
try
{
    Console.WriteLine(100 / int.Parse("0"));
}
catch (Exception ex)
{
    Console.WriteLine("Error: " + ex.Message);
}
catch (DivideByZeroException) // ✗
{
    Console.WriteLine("Zero");
}
TEXT
error CS0160: A previous catch clause already catches all exceptions of this or of a super type ('Exception')

Программа не компилируется. catch (Exception ex) стоит первым и ловит всё, в том числе DivideByZeroException, поэтому второй блок не выполнился бы никогда. Компилятор замечает это заранее: блок выше ждёт базовый класс Exception, а значит, уже перехватывает и DivideByZeroException. Поменяйте блоки местами: DivideByZeroException выше, Exception ниже.

Главное

  • Код, который может выбросить исключение, ставят в try, а обработчик — в catch с классом исключения в скобках. После обработчика программа идёт дальше.
  • После исключения остаток try пропускается. Текст сообщения лежит в ex.Message.
  • Блоки catch проверяются сверху вниз, выполняется первый подходящий; when добавляет к классу условие.
  • Конкретные классы пишут выше общего Exception, иначе компилятор сообщает об ошибке CS0160.