1. Вступ
Операції з файлами майже завжди повʼязані з зовнішніми щодо програми обставинами: файл можуть видалити, перемістити, його може заблокувати інша програма, раптом у користувача закінчилися права або вільне місце на диску. Усе це може призвести до винятків — спеціальних ситуацій, коли виконання програми переривається і «викидається» обʼєкт винятку (Exception). Якщо не перехопити й не обробити такий виняток, ваш застосунок завершиться з помилкою і покаже користувачеві моторошний червоний текст у консолі (або, що ще гірше, — мовчки впаде).
У C# для обробки таких ситуацій використовується блок try-catch (або, простими словами, «пастка помилок»). Він дозволяє не лише «спіймати» помилку, а й спокійно вирішити, що робити далі: показати дружнє повідомлення, запропонувати обрати інший файл, записати помилку в журнал або просто повторити спробу.
У реальних застосунках
Якщо ви пишете консольний калькулятор, то, мабуть, можна обійтися і без перехоплення помилок під час роботи з файлами. А от якщо у вас застосунок для обробки документів на підприємстві або гра, що зберігає прогрес, — не обробляти такі помилки все одно що ходити канатом без страховки: рано чи пізно щось та й станеться.
2. Згадуємо роботу з винятками
Базовий синтаксис try-catch
Перш ніж перейти до конкретних ситуацій із файлами, нагадаємо базовий синтаксис конструкції try-catch (яку вже розглядали в лекціях про винятки):
try
{
// Тут код, який може "викинути" виняток
}
catch (Exception ex)
{
// Тут ми ловимо всі можливі винятки... Але краще так не робити!
Console.WriteLine("Щось пішло не так: " + ex.Message);
}
У блоці try ми розміщуємо потенційно небезпечний код, а в catch — те, що робити, якщо тут щось пішло не так.
Жарт програміста: «try-catch — це цифровий аналог шапки-невидимки для помилок. Помилка є, але ви її не бачите!»
Файлові винятки
Коли ви працюєте із файлами у .NET, найчастіше стикаєтеся з винятками — скажімо, під час створення потоку, читання або запису. Ось кілька типових сценаріїв і повʼязаних із ними винятків:
- Файл не знайдено → FileNotFoundException
- Немає доступу до файлу/теки → UnauthorizedAccessException
- Проблеми зі шляхом (наприклад, неприпустимі символи, надто довгий шлях) → PathTooLongException, ArgumentException
- Файл уже використовується іншим процесом → IOException
- Немає вільного місця на диску → IOException
Найчастіше з файловими операціями повʼязані саме похідні від IOException. Усі вони успадковані від базового типу System.Exception.
3. Практика: перехоплюємо й обробляємо помилки під час роботи з файлами
Розгляньмо типові випадки на прикладах нашого «застосунку, що еволюціонує»: припустімо, ми намагаємося зчитати привітання з файлу, обробити його й вивести користувачеві. Додамо сюди перевірку на помилки за допомогою try-catch.
Приклад 1: Обробка «файл не знайдено»
string filePath = "hello.txt";
try
{
string greeting = File.ReadAllText(filePath);
Console.WriteLine("Вміст файлу: " + greeting);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Файл не знайдено! Перевірте, що файл " + filePath + " існує.");
// Можемо додатково вивести подробиці
Console.WriteLine("Технічні деталі: " + ex.Message);
}
Якщо файл "hello.txt" відсутній, програма не завершиться з помилкою, а виведе дружнє повідомлення. Так ми робимо застосунок стійким до типових помилок користувача.
Приклад 2: Порушення прав доступу
Візьмімо складнішу ситуацію: файл є, але в нас немає прав на його читання (наприклад, хтось навмисне дав права лише на запис або файл лежить у захищеній теці).
try
{
string secret = File.ReadAllText("C:\\Windows\\System32\\config.txt");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Немає доступу до файлу! Спробуйте запустити застосунок від імені адміністратора.");
Console.WriteLine("Технічна причина: " + ex.Message);
}
Приклад 3: Загальний IOException
Деякі помилки можуть бути повʼязані з конфліктом блокувань (наприклад, інший процес утримує файл відкритим), із нестачею місця на диску чи проблемами з обладнанням:
try
{
File.WriteAllText("important.txt", "Важлива інформація!");
}
catch (IOException ex)
{
Console.WriteLine("Помилка під час роботи з файлом: найімовірніше, його використовує інший застосунок або на диску недостатньо місця.");
Console.WriteLine("Технічна причина: " + ex.Message);
}
4. Перехоплення кількох винятків: вибіркова обробка
Іноді потрібно по-різному реагувати на різні типи винятків. У .NET дозволяється вказувати одразу кілька блоків catch — від більш конкретних до загальніших (інакше компілятор влаштує вашому коду гучний італійський страйк).
try
{
string content = File.ReadAllText("file.txt");
Console.WriteLine(content);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Файл не знайдено.");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Немає доступу до файлу!");
}
catch (IOException ex)
{
Console.WriteLine("Інша помилка введення-виведення: " + ex.Message);
}
Важливо: якщо поставити catch (Exception ex) першим, решта блоків не матимуть сенсу, адже базовий тип перехопить усе!
5. Вкладені try-catch і повторні спроби
Іноді ви хочете не лише обробити помилку, а й дати користувачеві шанс «виправитися» — наприклад, запропонувати ввести шлях до наявного файлу:
string filePath;
string content;
int attempts = 0;
const int maxAttempts = 3;
do
{
Console.Write("Введіть шлях до файлу: ");
filePath = Console.ReadLine();
try
{
content = File.ReadAllText(filePath);
Console.WriteLine("Вміст файлу:\n" + content);
break;
}
catch (FileNotFoundException)
{
Console.WriteLine("Файл не знайдено! Спробуйте ще раз.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Немає доступу до файлу! Спробуйте інший файл.");
}
attempts++;
}
while (attempts < maxAttempts);
if (attempts == maxAttempts)
Console.WriteLine("Надто багато невдалих спроб.");
Цей код — маленький «інтерфейс дружби» для користувача, який забув, куди зберіг файл.
6. Типові помилки й особливості: від лінощів до усвідомленості
Дуже поширена помилка новачків (і навіть бувалих розробників) — ловити взагалі всі винятки підряд, писати просто catch (Exception) і виводити «Сталася помилка!», не замислюючись про причини. Такий підхід поганий із кількох причин. По‑перше, він приховує реальні недоліки в бізнес‑логіці застосунку. По‑друге, інші, не повʼязані з файлами помилки (наприклад, друкарські помилки в коді або помилка в математиці), можуть бути випадково проковтнуті, і пошук справжньої причини затягнеться надовго.
Набагато краще — явно перехоплювати лише ті помилки, які ви здатні осмислено обробити. Якщо незрозуміло, яка саме помилка сталася, краще дати їй «впасти» — тобто взагалі не ловити: нехай застосунок впаде, зате ви побачите стек викликів і зрозумієте, що треба полагодити.
Особливість: Деякі винятки можуть мати вкладені причини (InnerException). Їх зручно аналізувати для детальнішої діагностики, особливо коли ведете журнали помилок (логи).
Ще один нюанс — якщо після catch-блоку життя застосунку неможливе (наприклад, якщо не вдалося відкрити головний файл налаштувань), можна завершити виконання через return або навіть кинути виняток знову (throw;), щоб не плодити «зомбі‑застосунки» з половиною функцій.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ