1. Вступ
Уявіть, що ви пишете калькулятор для ділення двох чисел. Усе працює чудово, поки хтось не вирішить поділити на нуль:
int a = 10;
int b = 0;
int result = a / b; // Бум!
У цей момент програма «падає», і користувач бачить лякаюче повідомлення про помилку. Це й є виняткова ситуація — подія, що не повинна траплятися в «нормальному» сценарії роботи програми й потребує особливої уваги.
Винятки — це особливий механізм, за допомогою якого .NET повідомляє вашій програмі, що сталося щось нештатне, і виконання слід перервати або обробити ситуацію особливим чином.
Коли трапляється помилка виконання (наприклад, ділення на нуль, спроба звернення до відсутнього файлу або — старий знайомий — розіменування null), .NET «викидає» (throw) спеціальний об’єкт‑виняток. Далі розпочинається пошук обробника, який зможе «перехопити» (catch) ситуацію й знає, як відреагувати.
Чому не повертати «код помилки»?
Можна було б повертати код помилки — і багато давніх мов (Pascal, C, та й деякі сучасні API) так роблять. Але це незручно й небезпечно: легко забути перевірити код помилки (бо усі сподіваються на краще) — і дуже складно зрозуміти, де саме все пішло не так. Винятки дають змогу централізовано відстежувати будь‑які помилки й реагувати на них гнучко, не захаращуючи основний код купою перевірок.
2. Генерування винятку
Винятки можуть виникати автоматично — під час помилки середовища виконання, або бути згенеровані вручну за допомогою ключового слова throw:
int[] arr = new int[2];
arr[10] = 5; // виняток буде згенеровано автоматично: System.IndexOutOfRangeException
throw new Exception("Абсолютна катастрофа!"); // вручну генеруємо виняток Exception
Навіть якщо вам здається, що ваша програма ідеально спроєктована, завжди може трапитися щось неочікуване: користувач закрив файл, інтернет зник, хтось вимкнув живлення комп’ютера (ну майже).
На щастя, .NET надає не лише механізм генерування винятків, а й засоби для їхнього перехоплення та обробки — ними ми займемося трохи пізніше.
3. Життєвий цикл винятку: від throw до catch
Коли в коді трапляється щось неприємне, .NET запускає «екстрену процедуру»:
- Створює об’єкт винятку (наприклад, IndexOutOfRangeException).
- Викидає його за допомогою ключового слова throw.
- Шукає обробник: переглядає стек викликів знизу вгору (від поточного методу до викликача) й шукає перший блок catch, який відповідає типу згенерованого винятку.
- Якщо знайдено обробник — програма продовжує виконання всередині блоку catch.
- Якщо жодного обробника не знайдено — програма аварійно завершується й виводить «стек викликів» з деталями помилки.
Це схоже на те, як м’яч котиться сходами: доки не трапиться «приймач», виняток «стрибає» вгору стеком.
4. Ієрархія винятків у .NET
Дерево «батьків і дітей»
У .NET (як і в більшості ООП‑мов) винятки реалізовано у вигляді класів, що успадковуються один від одного й утворюють ієрархію.
Усі вони мають спільного предка — базовий клас System.Exception.
System.Object
└─ System.Exception
├─ System.SystemException
│ ├─ System.NullReferenceException
│ ├─ System.IndexOutOfRangeException
│ ├─ System.DivideByZeroException
│ ├─ System.OutOfMemoryException
│ └─ ... і багато інших
├─ System.IO.IOException
│ ├─ System.IO.FileNotFoundException
│ ├─ System.IO.DirectoryNotFoundException
│ └─ ...
├─ System.ArgumentException
│ ├─ System.ArgumentNullException
│ └─ System.ArgumentOutOfRangeException
└─ (твої власні Exception-класи)
Чому класи?
Завдяки ієрархії класів можна перехопити одразу цілу «сім’ю» проблем — наприклад, усі помилки, пов’язані з аргументами методів (ArgumentException та його нащадки). Ба більше, це дає змогу додавати власні типи винятків (про це — на наступних лекціях).
Найпоширеніші винятки .NET
- NullReferenceException — спроба звернення до методу або властивості об’єкта, який дорівнює null. Старий знайомий!
- DivideByZeroException — ділення на нуль.
- IndexOutOfRangeException — вихід за межі масиву.
- ArgumentException, ArgumentNullException, ArgumentOutOfRangeException — некоректні аргументи методів.
- FileNotFoundException, IOException — проблеми під час роботи з файлами.
- InvalidOperationException — операція неможлива у поточному стані об’єкта.
- FormatException — некоректний формат даних (наприклад, спроба перетворити "abcd" у число).
5. Приклади: як виникають різні винятки
Розгляньмо для наочності приклади коду, що спричиняють різні винятки.
Приклад 1: NullReferenceException
string? str = null;
Console.WriteLine(str.Length); // Тут «Бум!» — str дорівнює null
Вивід:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
Приклад 2: DivideByZeroException
int a = 42;
int b = 0;
int c = a / b; // Небезпечно! Ділення на 0
Вивід:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
Приклад 3: IndexOutOfRangeException
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[5]); // Немає елемента з індексом 5
Тут явне звернення за межі масиву, і вивід такий:
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
Приклад 4: FileNotFoundException
using System.IO;
string content = File.ReadAllText("secret.txt");
Якщо у вас немає файлу з назвою "secret.txt", то отримаєте приблизно таке повідомлення:
Unhandled exception. System.IO.FileNotFoundException: Could not find file '/Users/zapp/RiderProjects/ConsoleApp1/ConsoleApp1/bin/Debug/net9.0/secret.txt'.
Приклад 5: FormatException
string input = "trinadtsyat";
int number = int.Parse(input); // Рядок не число
Вивід:
Unhandled exception. System.FormatException: The input string 'trinadtsyat' was not in a correct format.
6. Як використовувати отримані знання?
Розуміння того, як працюють винятки, дуже важливе для будь‑якого розробника. Це не просто «теорія заради теорії», а практична навичка, що безпосередньо впливає на стабільність ваших програм. Коли ви знаєте, як правильно обробляти помилки, програма не «падатиме» при першому ж збої, а натомість спокійно й зрозуміло повідомить користувача, що пішло не так. Це робить її надійнішою, професійнішою і просто дружнішою до людей.
Йдеться не лише про зручність. На співбесідах часто питають про механіку обробки винятків, типи Exception і як загалом працює цей механізм у .NET. Тож добре розуміння теми — це ще й додаткова перевага під час працевлаштування.
До того ж ви ще не раз зіштовхнетеся з винятками у реальних проєктах. Робота з файлами, мережею, базами даних, сторонніми бібліотеками та фреймворками — усе це активно використовує систему винятків. Тому краще відразу розібратися, як це влаштовано, щоб потім не було сюрпризів.
7. Типові помилки під час роботи з винятками
Помилка № 1: ігнорування винятків як таких.
Деякі новачки (а іноді й досвідчені розробники) сприймають винятки як щось шкідливе або таке, що заважає. Вони або повністю ігнорують їх, або, що ще гірше, обгортають код у catch { } і нічого не роблять. У результаті програма «мовчки» продовжує роботу в дивному стані, і відстежити причину збою стає неможливо.
Помилка № 2: плутанина між винятками й звичайними помилками.
Часто забувають, що не кожну помилку треба ловити через try-catch. Наприклад, якщо користувач увів некоректні дані, краще спочатку перевірити їх вручну, а не сподіватися на FormatException і тим більше не використовувати винятки як механізм керування логікою. Винятки — це про виняткові випадки, а не про рутину.
Помилка № 3: неправильне розуміння ієрархії винятків.
Іноді намагаються перехоплювати занадто загальний клас (Exception) і в результаті ловлять усе підряд, включно з тими помилками, які краще не обробляти. А іноді навпаки — ловлять занадто вузькі типи (IndexOutOfRangeException, NullReferenceException), забуваючи, що можуть виникнути й інші. Важливо розуміти, як улаштована ієрархія винятків у .NET і що саме ви хочете обробляти.
І головне — пам’ятайте: винятки в C# — це не аварія і не сигнал «усе пропало», а просто механізм перемикання на особливий сценарій виконання. Це потужний інструмент, якщо користуватися ним правильно.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ