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 обрабатывает определённый класс ошибок — от отсутствия файла или папки до проблем с правами доступа и общей ошибки ввода-вывода. Благодаря этому программа не "падает" при первом же сбое, а сообщает пользователю, что именно пошло не так, и что можно сделать. Такой подход делает приложение более надёжным и дружелюбным.

2
Задача
C# SELF, 38 уровень, 0 лекция
Недоступна
Обработка исключения FileNotFoundException
Обработка исключения FileNotFoundException
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ