Что такое исключение
Чему вы научитесь:
- читать отчёт о необработанном исключении и находить по нему место ошибки;
- перехватывать исключения блоками
tryиcatchи выбирать обработчик по классу; - объяснять, когда выполняется
finally, в том числе послеreturn; - выбрасывать свои исключения через
throwи освобождать черезusingто, что держит объект.

Представьте офисное здание. На пятом этаже задымилась розетка. Сотрудник не тушит её сам: он нажимает кнопку пожарной тревоги.
Сигнал идёт с этажа на этаж — на четвёртый, на третий, и так до охраны на первом. Кто-то по пути должен ответить на тревогу. Если не ответит никто, здание эвакуируют, и работа в нём останавливается целиком.
Программа устроена похоже. Этажи — это вызовы методов: внизу Main; над ним метод, который он вызвал; выше следующий вызов. Это тот же стек вызовов из темы о методах: последний вызов лежит сверху.
Когда метод не может выполнить свою работу, он поднимает тревогу: выбрасывает исключение. Исключение спускается по цепочке вызовов этаж за этажом: от метода к тому, кто его вызвал, и так до Main. Если его никто не перехватит, программа остановится.
У этой аналогии есть предел. В здании этажи, мимо которых идёт сигнал, продолжают работать, а в программе каждый метод на пути исключения прерывается.
С исключениями вы уже встречались. Каждое из них останавливало программу:
FormatException—int.Parseполучил строку, в которой нет числа;DivideByZeroException— целое число разделили на ноль;OverflowException— число переполнилось внутриchecked;IndexOutOfRangeException— индекс вышел за границы массива;ArgumentOutOfRangeException—Substringпопросили кусок за концом строки;KeyNotFoundException— в словаре нет такого ключа;NullReferenceException— к члену объекта обратились черезnull.
Посмотрим, что происходит, когда ошибка случается не в инструкциях верхнего уровня, а в методе, который они вызвали:
Console.WriteLine("Start"); // Start
int total = Sum("12", "ten");
Console.WriteLine(total);
int Sum(string a, string b)
{
int x = int.Parse(a);
int y = int.Parse(b); // ✗
return x + y;
}Путь исключения
Давайте разберём программу по шагам:
Console.WriteLine("Start")печатаетStart.Sum("12", "ten")вызывает метод, и инструкции верхнего уровня ждут его результат.- В
Sumстрокаint x = int.Parse(a);кладёт вxчисло12. int.Parse(b)не может превратить"ten"в число и выбрасываетFormatException.Sumпрерывается на этой строке:return x + y;не выполняется, и результата нет.- Исключение возвращается к строке с вызовом
Sum. Там его тоже никто не перехватывает, поэтомуConsole.WriteLine(total)не выполняется. - Программа печатает отчёт об ошибке и останавливается.
Первая строка отчёта:
Unhandled exception. System.FormatException: The input string 'ten' was not in a correct format.Под ней идут строки, которые начинаются с at. Это путь исключения по цепочке вызовов, от места, где его выбросили, до точки входа <Main>$. Первыми стоят методы самой .NET, например System.Int32.Parse.
Ниже — ваш Sum с пометкой line 8 и <Main>$ с пометкой line 2: это номера строк файла. По ним вы и найдёте в отчёте место ошибки. Имя Sum там записано как <<Main>$>g__Sum|0_0: так компилятор называет метод, объявленный среди инструкций верхнего уровня.
Исключение, которое так никто и не перехватил, называют необработанным. Это и значат первые слова отчёта: Unhandled exception. На собеседовании часто просят объяснить, что с ним происходит: оно проходит всю цепочку вызовов, и программа завершается с отчётом.
Исключение — это объект
System.FormatException — полное имя класса: пространство имён System и имя FormatException. В момент ошибки .NET создаёт объект этого класса и кладёт в него сообщение. В отчёте видно и то, и другое:
| Часть отчёта | Что она значит |
|---|---|
Unhandled exception. | исключение никто не перехватил |
System.FormatException | класс исключения: что за ошибка случилась |
The input string 'ten' was… | сообщение: подробности этой ошибки |
Классы исключений связаны наследованием: все они производные от одного базового класса Exception. Класс говорит, какая беда случилась, а сообщение — что именно пошло не так.
Ошибка компиляции и исключение
Ошибку компиляции находит компилятор ещё до запуска. Программа с ней не запустится вовсе, а в сообщении будет код вроде CS1002.
Исключение возникает во время работы и зависит от данных. Программа с Sum("12", "ten") компилируется без единого предупреждения. Та же программа с Sum("12", "30") отработала бы без ошибок и напечатала 42. Компилятор не знает, какой текст придёт в a и b, поэтому такую ошибку видно только при запуске.
Частая ошибка
Нужно сложить числа из массива строк. Ждут, что программа пропустит строку "x" и напечатает 8:
string[] items = ["3", "x", "5"];
int sum = 0;
foreach (string item in items)
{
sum += int.Parse(item); // ✗
}
Console.WriteLine(sum);Unhandled exception. System.FormatException: The input string 'x' was not in a correct format.Программа компилируется, но на втором элементе останавливается. Исключение прерывает не один проход цикла, а весь foreach и всю программу: до Console.WriteLine(sum) дело не доходит. Чтобы пропускать такие строки, их проверяют через int.TryParse из темы о методах или перехватывают исключение.
Главное
- Метод, который не может выполнить работу, выбрасывает исключение, и оно идёт назад по цепочке вызовов до
Main. - Каждый метод на этом пути прерывается: строки после места ошибки не выполняются.
- Необработанное исключение останавливает программу. Первая строка отчёта называет класс и сообщение, строки
at— путь и номера строк. - Исключение — объект класса, производного от
Exception, с сообщением внутри.