JavaRush /Курси /C# SELF /Типові винятки під час роботи з файлами

Типові винятки під час роботи з файлами

C# SELF
Рівень 38 , Лекція 0
Відкрита

1. Вступ: чому файли інколи «капризують»?

Буває, відкриваєте документ — і раптом система каже: «Файл не знайдено». Або спроба щось зберегти закінчується повідомленням: «Доступ заборонено». Це саме ті випадки, коли з файлами відбувається щось дивне. Якщо з кодуваннями ми стикаємося як із «набором незрозумілих символів», то тут проблеми вже пов’язані із самою файловою системою.

Файлову систему можна уявити як велику бібліотеку, а застосунок — як бібліотекаря. І коли він запитує файл, можливі різні відповіді:

  • Книга (тобто файл) відсутня — її просто не існує.
  • Розділу бібліотеки, де мала б бути книга, теж немає — відсутній потрібний каталог.
  • Книга «зачинена» або видана іншому — файл використовується іншим процесом або немає прав доступу.
  • Полиця заповнена — недостатньо місця на диску для створення нового файла.
  • Або, наприклад, спроба покласти книгу у відділ повернення DVD — тобто виконується непідтримувана операція.

У мові C# усі ці ситуації виражаються через винятки. І завдання розробника — не просто спостерігати, як програма «падає», а вміти передбачити можливі проблеми й обробити їх акуратно. Ніхто не хоче, щоб користувач бачив загадкове повідомлення про помилку з купою незрозумілого тексту.

Ми вже знайомі з конструкцією try-catch. Це наше рятівне коло, яке дозволяє спіймати виняток і вжити дії, замість того щоб дати програмі аварійно завершитися.

// Це наш старий знайомий, нагадування з Лекції 57
try
{
    // Тут пишемо код, який може спричинити помилку
    // Наприклад, спроба читання файла
}
catch (Exception ex) // Ловимо будь-який виняток
{
    // Тут ми обробляємо помилку
    Console.WriteLine($"Ой, сталася помилка: {ex.Message}");
}

Сьогодні ми заглибимося у специфічні винятки, які виникають під час роботи з файлами. Це дозволить писати надійніший код, який уміє спілкуватися з файловою системою, навіть коли вона «капризує».

Винятки — це не «баги», а сигнали тривоги!

Важливо розуміти: виняток — це не завжди помилка у вашому коді. Часто це сигнал про те, що щось пішло не так у зовнішньому середовищі, з яким ваш код взаємодіє. Файлова система — яскравий приклад такого зовнішнього середовища. Ви можете написати абсолютно правильний код для читання файла, але якщо користувач видалив цей файл до того, як ваша програма встигла його прочитати, ви отримаєте виняток. І це нормально! Ваше завдання як розробника — навчити програму реагувати на такі ситуації.

Розгляньмо найчастіші «сигнали тривоги», які ви можете зустріти під час роботи з файлами.

2. FileNotFoundException: файлу й не було

Мабуть, це найпоширеніший виняток під час роботи з файлами. Він виникає, коли ви намагаєтеся відкрити, прочитати або виконати іншу операцію над файлом, якого не існує за вказаним шляхом.

Приклад із життя: ви просите друга принести вам книгу «Програмування на C# 14 для чайників» зі своєї бібліотеки, а він відповідає: «Такої книги в мене немає». Точно так само ваша програма може попросити операційну систему: «Дай мені файл settings.txt», а система відповість: «Вибачте, такого немає».

Спробуймо написати код, який читає файл abracadabra.txt для нашого застосунку‑менеджера задач. Якщо файла немає, ми маємо повідомити про це користувача, а не просто «впасти».

try
{
    using var reader = new StreamReader("abracadabra.txt");
    Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("Файл не знайдено: " + ex.FileName);
}

У побуті це як прийти на зупинку, а автобуса немає — і маршрутка теж не під’їжджає. Неприємно.

Візьміть до уваги: часто цей виняток супроводжується ще й неправильним шляхом до файла (наприклад, ви забули, що працюєте з іншого каталогу).

3. DirectoryNotFoundException: каталоги кудись зникли

Цей виняток дуже схожий на FileNotFoundException, але стосується не самого файла, а каталогу (папки), у якому цей файл має міститися. Якщо ви вказуєте шлях, наприклад, "C:\MyDocuments\MyProject\Data\report.txt", а каталогу Data не існує, то отримаєте DirectoryNotFoundException.

У нашому застосунку, якби ми хотіли зберігати налаштування в підкаталозі data, наприклад, "./data/app_settings.txt", і цього каталогу не було б, то під час спроби запису або читання файла ми зіткнулися б із цією проблемою.

DirectoryNotFoundException можна ловити окремо, як FileNotFoundException, або ж він може бути частиною більш загального IOException, про який поговоримо трохи пізніше.

try
{
    using var writer = new StreamWriter(@"C:\very\strange\path\file.txt");
    writer.WriteLine("Hello world");
}
catch (DirectoryNotFoundException ex)
{
    Console.WriteLine("Каталог не знайдено!");
}

Поширена помилка: каталог може зникнути будь‑якої миті (наприклад, хтось чистить тимчасові файли) або у вас неправильно налаштований шлях для запису.

4. UnauthorizedAccessException: вхід заборонено!

Уявіть, що ви хочете покласти книгу на полицю, а там висить табличка «Доступ лише для співробітників». Це і є UnauthorizedAccessException! Він виникає, коли у вашої програми немає необхідних прав доступу до файла або каталогу. Це може бути через:

  • Недостатні права користувача: ви намагаєтеся записати файл у папку, куди може писати лише адміністратор (наприклад, C:\Windows).
  • Файл позначено як «лише для читання»: ви намагаєтеся змінити файл, який має атрибут «лише для читання».
  • Файл системний або прихований: і до нього є специфічні обмеження.

Це дуже поширена проблема в корпоративних середовищах або коли користувачі встановлюють програми у захищені каталоги.

Спробуймо записати файл у системну папку, куди у звичайного користувача немає прав. (Увага: запускати такий код варто обережно, щоб не захаращувати системні каталоги, або в «пісочниці»).

try
{
    using var writer = new StreamWriter("/system/settings.conf");
    writer.WriteLine("Вся влада студентам!");
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine("Немає доступу до файла або каталогу!");
}

Якщо запустите цей код без прав адміністратора, швидше за все, побачите повідомлення «Доступ до цієї папки заборонений». Якщо запустите його від імені адміністратора, файл, ймовірно, буде створено. Цей приклад показує, як важливо обробляти UnauthorizedAccessException, щоб користувач зрозумів, чому застосунок не може виконати операцію.

5. IOException: універсальне «ой‑ой» файлової системи

IOException — це найзагальніший виняток, пов’язаний з операціями вводу‑виводу. Цей виняток викидає система, коли виникає якась проблема з самим пристроєм вводу‑виводу або з файловою системою, яка не підпадає під більш специфічні винятки, такі як FileNotFoundException або UnauthorizedAccessException.

Типові сценарії, коли ви можете отримати IOException:

  • Файл уже використовується іншою програмою: наприклад, ви намагаєтеся видалити файл, який відкрито в Блокноті або в іншій вашій програмі.
  • Диск переповнений: недостатньо місця на диску для запису файла.
  • Пошкоджений файл або файлова система: рідко, але буває.
  • Мережеві проблеми: якщо файл знаходиться на мережевому диску і зв’язок обірвався.
  • Імʼя файла або каталогу надто довге. (Це може бути і PathTooLongException, але інколи потрапляє під IOException).

IOException — свого роду «універсальний ключ» для багатьох проблем. Часто, коли ви ловите IOException, варто також подивитися на його властивість Message, щоб отримати детальнішу інформацію про те, що саме пішло не так.

try
{
    using var file = new FileStream("busyfile.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.None);
    // якимось чином тримаємо файл відкритим
    // водночас в іншому місці:
    using var writer = new StreamWriter("busyfile.txt");
    writer.WriteLine("Спроба запису...");
}
catch (IOException ex)
{
    Console.WriteLine("Помилка вводу‑виводу: " + ex.Message);
}

Важливий момент: IOException — базовий клас для багатьох інших «файлових» винятків.

6. Інші, але не менш важливі «неприємності»

PathTooLongException

Це менш поширена, але цілком реальна проблема: ваш шлях (або імʼя файла/каталогу) надто довгий для операційної системи. Наприклад, якщо ви вирішите закласти в назву файла короткий зміст «Війна і мир», Windows вам цього не пробачить.

У Windows історичне обмеження — 260 символів для повного шляху. Нові версії ОС і .NET дозволяють вмикати «довгі шляхи», але це не завжди працює за замовчуванням.

try
{
    string veryLongPath = new string('a', 300); // 300 символів!
    using var writer = new StreamWriter(veryLongPath + ".txt");
    writer.WriteLine("Це надто довге імʼя файла!");
}
catch (PathTooLongException ex)
{
    Console.WriteLine("Імʼя файла або шлях надто довгі!");
}

NotSupportedException

Це рідкісний, але «сюрреалістичний» випадок, коли ви передаєте у конструктор типу StreamReader або FileStream некоректний рядок шляху, наприклад, із забороненими символами, або використовуєте «магічні» шляхи типу C:::\wow???\file.txt.

7. Корисні нюанси

Які винятки за що відповідають

Виняток Причина виникнення Приклад ситуації
FileNotFoundException
Файл не знайдено Відкриття неіснуючого файла
DirectoryNotFoundException
Каталог не знайдено Відкриття файла у видаленому каталозі
UnauthorizedAccessException
Немає доступу (прав) Запис у захищений каталог
IOException
Загальна помилка вводу‑виводу Файл відкрито іншим процесом
PathTooLongException
Надто довгий шлях Надто довге імʼя файла/папки
NotSupportedException
Некоректний формат шляху Шлях із забороненими символами

Поширені помилки та спеціальні випадки

Приклад: файл зайнятий іншим процесом

Уявіть, що ви відкрили текстовий файл у Notepad і забули його закрити. У цей момент ваша програма намагається записати в той самий файл. Ось тут і приходить IOException (або навіть «sharing violation»).

Приклад: відсутність доступу

Спробуйте зберегти дані в C:\Windows без адміністративних прав — отримаєте UnauthorizedAccessException. Те саме може статися, якщо ви відкрили файл лише для читання, а намагаєтеся щось у нього записати.

Приклад: некоректний шлях

У Windows не можна давати файлам імена з символами <>:"/\|?*. Якщо спробуєте записати такий файл — програма повідомить про NotSupportedException (або ArgumentException).

Приклад: немає місця на диску

Це, як не дивно, теж видає IOException — наприклад, коли диск переповнений (ось чому корисно час від часу згадувати про каталог Downloads).

Як не потрапити в халепу: найкращі практики виявлення помилок

  • Перевіряйте існування файла за допомогою File.Exists і Directory.Exists до спроби відкрити файл. Але будьте обережні: файл може зникнути або з’явитися вже після перевірки (класична race condition).
  • Ніколи не «ковтайте» винятки повністю (не робіть просто catch { }), якщо лише ви не реалізуєте спеціалізований логер помилок. Завжди принаймні логуйте або показуйте користувачеві, що сталося.
  • Намагайтеся перехоплювати конкретні винятки (FileNotFoundException, DirectoryNotFoundException), а не лише загальний Exception.
  • Для кросплатформних застосунків враховуйте, що права доступу, формати шляхів і довжини імен файлів різняться на Windows, Linux, macOS.
  • Якщо обробляєте текстові файли, завжди явно вказуйте потрібне кодування — інакше не уникнути сюрпризів.
  • Для масових операцій із файлами бажано використовувати «bulk»‑обробку з урахуванням можливих збоїв на кожному кроці.

Зведена шпаргалка з типових винятків

Виняток Коли виникає? Як запобігти/обробити?
FileNotFoundException
Немає файла за вказаним шляхом Перевіряйте файл за допомогою File.Exists або створіть його
DirectoryNotFoundException
Шлях містить неіснуючий каталог Перевіряйте шлях, створюйте каталоги за допомогою Directory.CreateDirectory
UnauthorizedAccessException
Немає прав на файл/каталог, файл лише для читання, зайнятий ін. процесом Запускайте від потрібного користувача, перевіряйте ACL, коректно закривайте файли
IOException
Загальна помилка вводу‑виводу, файл зайнятий, немає місця на диску Використовуйте try-catch, намагайтеся не тримати файли відкритими
PathTooLongException
Надто довгий шлях або імʼя файла Скорочуйте шлях, використовуйте відносні шляхи
NotSupportedException
Некоректний формат шляху Перевіряйте рядок шляху на заборонені символи

Блок‑схема «Що робити при помилці з файлом?»

flowchart TD
    A[Операція з файлом] --> B{Викинуто виняток?}
    B -- Ні --> C[Операцію завершено успішно]
    B -- Так --> D{Який тип винятку?}
    D -- FileNotFound --> E[Попросити користувача вказати правильний файл або створити його]
    D -- DirectoryNotFound --> F[Створити відсутній каталог]
    D -- UnauthorizedAccess --> G[Попросити користувача перезапустити з потрібними правами]
    D -- IOException --> H[Перевірити, хто тримає файл, перевірити диск]
    D -- PathTooLong --> I[Скоротити шлях]
    D -- NotSupported --> J[Перевірити формат шляху]
    D -- Other --> K[Показати повідомлення й переперевірити логіку]

8. Як типові винятки виглядають у реальному застосунку?

Розвиваючи наш навчальний проєкт, припустімо, що маємо міні‑програму, яка зберігає нотатки користувача у файл, а потім читає їх.

string notesPath = "notes.txt";

Console.Write("Введіть нотатку: ");
string note = Console.ReadLine();

try
{
    // Зберігаємо нотатку
    using var writer = new StreamWriter(notesPath, true, Encoding.UTF8);
    writer.WriteLine(note);

    // Читаємо всі нотатки
    Console.WriteLine("Ваші нотатки:");
    using var reader = new StreamReader(notesPath, Encoding.UTF8);
    Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException)
{
    Console.WriteLine("Файл нотаток не знайдено. Спробуйте створити його вручну.");
}
catch (DirectoryNotFoundException)
{
    Console.WriteLine("Шлях до файла нотаток некоректний. Перевірте, чи існує папка.");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Немає прав на запис або читання файла. Запустіть програму від адміністратора.");
}
catch (IOException ex)
{
    Console.WriteLine("Сталася помилка вводу‑виводу: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("Неочікувана помилка: " + ex.Message);
}

У цьому прикладі показано типову ситуацію, з якою може зіткнутися будь‑який застосунок: спроба зберегти дані у файл і потім прочитати їх назад. Кожен блок catch обробляє певний клас помилок — від відсутності файла або каталогу до проблем із правами доступу і загальної помилки вводу‑виводу. Завдяки цьому програма не «падає» при першому ж збої, а повідомляє користувачеві, що саме пішло не так і що можна зробити. Такий підхід робить застосунок надійнішим і дружнім.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ