try и catch
Вернёмся к пожарной тревоге. Пусть на одном из этажей сидит дежурный. Он слышит сигнал, разбирается с ним, и тревога дальше не идёт: здание работает как прежде. В C# такого дежурного ставят прямо в коде, и называется он блоком catch.
Аналогия неточна в одном. Дежурный, разобравшись с тревогой, возвращает людей на рабочие места. Программа после catch к месту ошибки не возвращается: строки блока try ниже этого места так и не выполнятся.
Как устроены try и catch
Код, который может выбросить исключение, ставят в блок try, а сразу за ним — блок catch:
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Давайте разберём программу по шагам:
Startпечатается как обычно.tryпо-английски значит «попробуй»: в его фигурных скобках стоит код, который может дать сбой.int.Parse("ten")выбрасываетFormatException. Остаток блокаtryпропускается:Console.WriteLine(n)не выполняется.catchзначит «поймай». Блокcatch (FormatException ex)ждёт исключения классаFormatException. Класс подходит, и блок печатает сообщение.- После
catchпрограмма идёт дальше и печатаетEnd. Исключение обработано, и программа не остановилась.
Блок catch называют обработчиком исключения. Когда обработчик выполняется, говорят, что он перехватил исключение.
Если заменить "ten" на "42", исключения не будет. Блок try выполнится целиком и напечатает 42, а catch не выполнится.
Переменная исключения
В скобках после catch стоят класс и имя переменной: FormatException ex. В ex лежит тот самый объект исключения, который создала .NET. Его свойство Message — сообщение, которое в отчёте шло после двоеточия.
Если объект не нужен, имя можно не писать: catch (FormatException). Блок по-прежнему ловит исключения этого класса.
Несколько блоков catch
У одного try может быть несколько блоков catch. Когда случается исключение, их проверяют сверху вниз и выполняют первый, чей класс подходит. Остальные пропускаются:
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. Такое условие называют фильтром: блок выполняется, только если подходит класс и условие истинно.
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.
Частая ошибка
Общий блок поставили выше конкретного:
try
{
Console.WriteLine(100 / int.Parse("0"));
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
catch (DivideByZeroException) // ✗
{
Console.WriteLine("Zero");
}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.