JavaRush /Курсы /C# SELF /Синтаксис try-catch: обработка ошибок

Синтаксис try-catch: обработка ошибок

C# SELF
13 уровень , 1 лекция
Открыта

1. Введение

В идеальном мире код должен быть идеальным. Но, к сожалению, даже самый аккуратный программист однажды столкнется с неожиданными ситуациями: не найден файл, пользователь ввёл строчку вместо числа, сеть внезапно перестала работать, или что-то вернулось из базы данных не так, как ожидалось.

Если такие проблемы не ловить — программа просто "упадёт", иногда с загадочным сообщением об ошибке и stack trace (стеком вызовов). Это не то, что ждет пользователь или будущий отдел поддержки. Нужно научиться "ловить" такие ошибки и реагировать на них: мирно завершать работу, выводить человекопонятное сообщение или даже исправлять ситуацию на лету.

try-catch — это способ сказать компилятору и среде выполнения: "Попробуй выполнить этот кусок кода. Если возникнет проблема, не паникуй, дай мне шанс разобраться!"

2. Базовый синтаксис try-catch

Общая структура очень проста:


try
{
    // Здесь мы пишем опасный (или потенциально опасный) код
}
catch (ExceptionType variableName)
{
    // Здесь мы пишем, что делать, если возникло исключение типа ExceptionType
}
Базовый синтаксис try-catch
  • Блок try — это "опасная зона". В него помещаем те операции, которые потенциально могут "выстрелить".
  • Блок catch — ловит исключения определённого типа. Если внутри try возникнет исключение, управление сразу перескочит в первый подходящий catch, и код после "проблемного места" в try уже выполняться не будет.

Пример: простая обработка исключения

Допустим, наш мини-калькулятор из прошлых лекций теперь умеет делить числа. Но пользователь может ввести ноль! Посмотрим, как это работает без обработки ошибок:

int a = 10;
int b = 0;
int result = a / b; // БУМ! System.DivideByZeroException
Console.WriteLine(result);

Программа аварийно завершится с ошибкой DivideByZeroException:

Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.

Теперь "починим" с помощью try-catch:

int a = 10;
int b = 0;

try
{
    int result = a / b; // опасная операция
    Console.WriteLine("Результат деления: " + result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Ой! Деление на ноль невозможно: " + ex.Message);
}

Теперь программа не "упадёт". В консоли появится сообщение о невозможности деления на нуль:

Ой! Деление на ноль невозможно: Attempted to divide by zero.

3. Как работает try-catch: шаг за шагом

На самом деле внутри try может быть сколько угодно строк кода. Если одно из действий внутри блока выбрасывает исключение, управление тут же перескакивает в ближайший подходящий catch. Всё, что написано после места ошибки в блоке try, выполняться не будет.

Рассмотрим пример покрупнее:

try
{
    Console.WriteLine("Начинаем...");
    int[] numbers = { 1, 2, 3 };
    Console.WriteLine(numbers[1]);    // это ок
    Console.WriteLine(numbers[5]);    // ошибка: индекс за пределами массива
    Console.WriteLine("Это сообщение уже не появится!");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Ошибка: Попытка обращения к несуществующему элементу массива");
}

Вывод в консоль будет таким:

Начинаем...
2
Ошибка: Попытка обращения к несуществующему элементу массива

Process finished with exit code 0.

Как работает эта программа?

  • Выводится сообщение "Начинаем...".
  • Затем программа попробует вывести элемент numbers[1] — это 2.
  • Когда доходит до numbers[5], происходит "авария", при которой "выбросится" IndexOutOfRangeException.
  • Программа тут же перескочит в блок catch, пропустив Console.WriteLine("Это сообщение уже не появится!");
  • В консоли появится наше сообщение из catch.

4. Можно ли обрабатывать разные типы ошибок по-разному?

Да! Мы можем указать несколько блоков catch, чтобы по-разному реагировать на разные типы исключений. Это удобно: например, если одно действие связано с чтением файла (может быть FileNotFoundException), а другое — с делением (может быть DivideByZeroException).

try
{
    // Ваш опасный код
}
catch (DivideByZeroException)
{
    Console.WriteLine("Ошибка деления на ноль");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Ошибка: индекс вне диапазона массива");
}
catch (Exception ex)
{
    Console.WriteLine("Какая-то непредвиденная ошибка: " + ex.Message);
}

Здесь последний блок catch с типом Exception — "универсальный ловец". Он поймает любые другие исключения, которые не подошли предыдущим блокам. Но помните: если вы напишете его первым, остальные до него уже не дойдут! Всегда ставьте "широкие" catch в конец.

5. Как выглядит объект исключения?

Внутри блока catch мы можем опционально указать переменную — например, catch (Exception ex). Она содержит всю информацию о произошедшей ошибке: сообщение, тип ошибки, стек вызовов, вложенные исключения.

Вот небольшой пример:

try
{
    string? text = null;
    Console.WriteLine(text.Length); // Опа! NullReferenceException
}
catch (NullReferenceException ex)
{
    Console.WriteLine("Поймано исключение: " + ex.Message);
    Console.WriteLine("Стек вызовов: " + ex.StackTrace);
}

Такой подход — спасение при отладке сложных ошибок: вы всегда сможете увидеть, где и почему программа "споткнулась".

6. Ловим ошибку и продолжаем работу

В реальных, особенно пользовательских, приложениях ошибка — это не конец света. Ну ввёл человек неправильный путь к файлу, всяко бывает. Это ещё не повод перезагружать программу в истерике. Вместо этого мы спокойно просим его попробовать ещё раз.

Пример ниже показывает простой, но рабочий подход: мы оборачиваем чтение файла в цикл, и если что-то пошло не так, просто сообщаем об ошибке и просим повторить попытку. Без паники, без красных экранов.

bool success = false;
while (!success)
{
    Console.Write("Введите имя файла: ");
    string fileName = Console.ReadLine() ?? "";

    try
    {
        string content = File.ReadAllText(fileName);
        Console.WriteLine("Файл успешно прочитан!");
        success = true; // УРА!
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine("Ошибка: файл не найден. Попробуйте ещё раз.");
    }
}

Здесь ключевая идея — не просто поймать исключение, а грамотно его обработать: мы не даём программе упасть, а переключаем её в режим "давай ещё раз, но правильно". Пользователь получает понятную подсказку, программа — второй шанс, а вы — благодарность и уважение.

7. Типичные ошибки при работе с try-catch

Ошибка №1: Оборачивать весь код в один универсальный try-catch.
Это выглядит заманчиво — один try, один catch (Exception), и вроде как "всё защищено". Но на деле вы теряете контроль над ситуацией. Непонятно, где именно произошёл сбой, и на какую ошибку вы на самом деле реагируете. В итоге, вместо надёжности — полная потеря диагностики.

Ошибка №2: Ловить исключения и ничего с ними не делать.
Пример из жизни:

try
{
    // что-то потенциально опасное
}
catch
{
    // молчание...
}
Такой код буквально говорит: "Что-то пошло не так... ну и ладно". Это опасно: программа может продолжить работу в неправильном состоянии, и вы даже не узнаете, что была ошибка. Всегда хотя бы выведите сообщение или залогируйте сбой. Никаких пустых catch!

Ошибка №3: Ловить слишком общий тип исключения.
Если вы ловите Exception, хотя знаете, что может возникнуть, скажем, FormatException или FileNotFoundException, — вы лишаете себя возможности адекватно отреагировать на конкретную проблему. Чем уже и точнее ваш catch, тем более предсказуемо и грамотно ведёт себя программа в нестандартной ситуации.

8. Для чего НЕ рекомендуется использовать исключения

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

try
{
    if (x < 0) throw new Exception("x должен быть >= 0");
}
catch (Exception)
{
    // откат состояния, продолжение работы...
}

Так делать не стоит! Исключения — это инструмент для неожиданных, редких ситуаций (ошибка чтения файла, сбой сети и т.п.), а не для обычных проверок на валидность данных пользователя. Для таких случаев используйте условные инструкции (if, else) и возвращайте специальные значения, если что-то не так.

2
Задача
C# SELF, 13 уровень, 1 лекция
Недоступна
Деление с обработкой ошибки
Деление с обработкой ошибки
2
Задача
C# SELF, 13 уровень, 1 лекция
Недоступна
Обработка ввода некорректных чисел
Обработка ввода некорректных чисел
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ra Уровень 35 Student
3 октября 2025
Интересно, если само исключение не важно, в шарпе можно писать catch (Exception) а в Java надо всегда писать что-то типа catch (Exception ignored) И в шарпе нет обязательных (контролируемых) исключений, тоже +
whynot00 Уровень 1
19 сентября 2025
по поводу первой ошибки, может я не правильно понял, но вроде бы обернуть Main в трай/кетч не будет антипаттерном. В кетче можно реализовать graceful shutdown, допустим закрыть файл/бд, залогировать ошибку и завершить программу.
Александр Уровень 24
26 октября 2025
лично видел как в одном приложении при клике на "красный крестик" выбрасывался кастомный эксепшн, а в catch как раз была реализована подобная логика - финализация файла, закрытие соединения с бд и что-то ещё. Но это же лютейшая дичь!