Підрозділ 5.4
Генерація винятку та оператор throw
Пояснює ручну генерацію винятків через throw, передачу повідомлення в Exception і повторне викидання винятку з catch.
5.4. Генерація винятку та оператор throw
До цього моменту ми розглядали лише перехоплення винятків, які генерує сама система. Але в C# будь-який метод може генерувати виняток вручну — оператором throw. Це ключовий інструмент для впровадження бізнес-правил і валідації даних.
Навіщо генерувати виняток вручну
Іноді дані технічно коректні (рядок не порожній, число у діапазоні), але порушують бізнес-правило. Наприклад:
- вік пацієнта не може бути від'ємним
- діагноз не може бути порожнім рядком
- ідентифікатор картки повинен починатися з певного префікса
У таких випадках система не знає, що дані некоректні — це знаємо лише ми. Оператор throw дозволяє сигналізувати про помилку стандартним механізмом винятків.


Форма 1: throw new — генерація нового винятку
throw new ExceptionType("повідомлення про помилку");Після throw вказується новий об'єкт будь-якого класу винятку. Рядок у конструкторі стає значенням властивості Message.
using System;
RegisterPatient("Іван Петренко", 45);
RegisterPatient("", 30);
RegisterPatient("Марія Сидоренко", -5);
void RegisterPatient(string name, int age)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Ім'я пацієнта не може бути порожнім.");
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(age),
$"Вік {age.ToString()} є неприпустимим значенням.");
Console.WriteLine($"Зареєстровано: {name}, {age.ToString()} р.");
}Цей код компілюється, але виконати його до кінця не вдасться: вже на другому виклику RegisterPatient("", 30) виняток ArgumentException ніхто не перехоплює, і програма аварійно завершується (Unhandled exception), а третій виклик так і не відбудеться. Загорнемо виклики в try...catch:
Зверніть увагу: throw у методі RegisterPatient не потребує try...catch всередині нього. Виняток «спливає» вгору до місця виклику, де і перехоплюється.
Throw у методах: валідація аргументів
Найпоширеніший патерн — перевіряти аргументи на початку методу і кидати ArgumentException або ArgumentNullException при порушенні:
Форма 2: throw без аргументів — перекидання винятку
Оператор throw без об'єкта можна використовувати лише всередині блоку catch. Він повторно кидає той самий виняток, що перехоплено — без змін і зі збереженням оригінального стека викликів:
Внутрішній catch логує помилку, але не пригнічує її — throw; передає виняток зовнішньому блоку. Стек викликів залишається незмінним: зовнішній обробник бачить оригінальне місце виникнення помилки.
throw як вираз і ThrowIfNull
З C# 7 throw може бути не лише інструкцією, а й виразом — у тернарному операторі, після ?? та в тілі-виразі =>. Це дозволяє компактно перевіряти аргументи:
public Patient(string name) =>
Name = name ?? throw new ArgumentNullException(nameof(name));Для найчастіших перевірок .NET має готові статичні методи: ArgumentNullException.ThrowIfNull(name) (.NET 6), ArgumentException.ThrowIfNullOrWhiteSpace(name) (.NET 8), ArgumentOutOfRangeException.ThrowIfNegative(age) (.NET 8). Вони самі підставляють ім'я параметра в повідомлення.
throw; vs throw ex; — важлива різниця
// Правильно — зберігає оригінальний StackTrace:
catch (Exception ex)
{
Log(ex);
throw;
}
// Небезпечно — скидає StackTrace до цього рядка:
catch (Exception ex)
{
Log(ex);
throw ex; // StackTrace тепер вказує на цей рядок, а не на першопричину
}throw ex; замінює стек викликів на поточне місце, що ускладнює діагностику — у логах не буде видно, де насправді сталася помилка. Завжди використовуйте throw; для перекидання.
Ланцюжок винятків через InnerException
Іноді доцільно «загорнути» низькорівневий виняток у вищорівневий — зі збереженням оригіналу як InnerException. Це дозволяє надати більш змістовний контекст, не втрачаючи першопричину:
Конструктор більшості класів винятків приймає другим параметром innerException. Завдяки цьому зовнішній код може перевірити ex.InnerException і отримати повний ланцюжок причин.
Підсумок
| Форма | Де використовується | Що робить |
|---|---|---|
throw new Ex("msg") |
Будь-де | Генерує новий виняток |
throw new Ex("msg", innerEx) |
Будь-де | Генерує виняток із збереженням причини |
throw; |
Лише у catch |
Перекидає поточний виняток, зберігаючи StackTrace |
throw ex; |
Будь-де, де є змінна винятку (зазвичай у catch) |
Кидає той самий об'єкт, але StackTrace починається заново з цього місця — уникайте |
throw у методі — це не помилка, а норма. Метод має «кидати» виняток, якщо не може виконати свою роботу коректно. Обробку помилки слід залишати тому рівню, який знає, що з нею робити.