1. Копирование файлов
До этого момента наши операции над файловой системой были чем-то вроде «одиночного удара»: создал файл, прочитал, удалил — и всё. Но в реальном мире часто встречается задача скопировать содержимое: резервное копирование документов, дублирование шаблонов, автоматизация процесса работы с данными. Копирование кажется детской задачей (Ctrl+C и Ctrl+V — наше всё), но на самом деле тут хватает нюансов.
Сегодня мы разберём все способы копирования файлов и папок — от самых простых до чуть более сложных. Мы также увидим, как встроенные классы .NET решают эту задачу, какие есть ограничения, какие ошибки могут подстерегать вас и что делать, если директорий вдруг оказалось две тысячи, а файлов — миллион.
Класс File и метод Copy
Самый простой способ копировать файл — воспользоваться статическим методом File.Copy. Этот метод принимает путь к исходному файлу, путь к новому файлу и опциональный параметр: разрешить ли перезапись, если файл уже существует.
using System.IO;
// Простой пример копирования файла
File.Copy("source.txt", "destination.txt");
Если файл назначения уже есть, метод выбросит исключение. Чтобы явно разрешить перезапись, используйте третий параметр:
File.Copy("source.txt", "destination.txt", overwrite: true);
Важный момент: Если второй параметр ("destination.txt") — путь к существующему каталогу, а не к файлу, то возникнет ошибка. Метод ожидает путь именно к файлу!
Работа с путями
Как и раньше, не забывайте использовать Path.Combine, чтобы не попасться на ловушках двойных или обратных слэшей:
string sourcePath = Path.Combine("Data", "input.txt");
string destPath = Path.Combine("Backup", "input_backup.txt");
File.Copy(sourcePath, destPath, overwrite: true);
Обработка ошибок
Что может пойти не так при копировании файла? Многое: файл может быть занят другим процессом, файл-источник может отсутствовать, у вас может не быть прав, диск назначения — переполнен. Используйте обработку исключений:
try
{
File.Copy("bigdata.txt", "bigdata_backup.txt", overwrite: false);
Console.WriteLine("Файл скопирован успешно!");
}
catch (IOException ex)
{
Console.WriteLine($"Ошибка ввода-вывода: {ex.Message}");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Нет доступа к файлу или папке.");
}
catch (Exception ex)
{
Console.WriteLine($"Другая ошибка: {ex.Message}");
}
Класс FileInfo и метод CopyTo
Если у вас уже есть объект FileInfo, вы можете вызвать на нём метод CopyTo:
var fi = new FileInfo("report.xlsx");
fi.CopyTo("backup_report.xlsx");
Третий аргумент (overwrite) в CopyTo появился только в .NET Core 2.0+, так что если вдруг пишете под старую версию фреймворка — не удивляйтесь ошибке.
Копирование файла «в никуда» (или «не пойман — не вор»)
Внимательно относитесь к указанному пути для копирования. Если папки назначения не существует, .NET выдаст ошибку. Поэтому перед копированием файла желательно убедиться, что директория-приёмник существует:
string backupDir = "Backup";
if (!Directory.Exists(backupDir))
{
Directory.CreateDirectory(backupDir);
}
string targetPath = Path.Combine(backupDir, "mydoc.txt");
File.Copy("mydoc.txt", targetPath);
2. Копирование директорий: задача не для слабонервных
Вот тут-то и начинаются настоящие приключения! В .NET нет встроенного «магического» метода Directory.Copy, который всё сделает за нас одной строкой (как у класса File). Придётся немного поработать руками и написать функцию для рекурсивного копирования всех файлов и подпапок.
Почему не существует Directory.Copy?
Копирование директорий не всегда тривиально. Нужно учесть, что каждая папка может содержать файлы, подпапки, скрытые файлы, файлы с особыми правами и длинными путями. Поэтому разработчики .NET решили: «Пусть программисты сами решают, что им копировать.» Но мы с вами не ищем лёгких путей — напишем свою функцию!
Пример рекурсивного копирования директории
Задача: скопировать содержимое одной папки (и всё её подмироздание) в другую.
using System;
using System.IO;
void CopyDirectory(string sourceDir, string destDir, bool recursive)
{
// Проверяем, существует ли исходная папка
if (!Directory.Exists(sourceDir))
throw new DirectoryNotFoundException($"Исходная папка не найдена: {sourceDir}");
// Создаём папку назначения, если её ещё нет
if (!Directory.Exists(destDir))
Directory.CreateDirectory(destDir);
// Копируем все файлы
foreach (string filePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(filePath);
string destFilePath = Path.Combine(destDir, fileName);
File.Copy(filePath, destFilePath, overwrite: true);
}
// Если рекурсивно — копируем все подпапки
if (recursive)
{
foreach (string dirPath in Directory.GetDirectories(sourceDir))
{
string dirName = Path.GetFileName(dirPath);
string destSubDir = Path.Combine(destDir, dirName);
// Рекурсивный вызов!
CopyDirectory(dirPath, destSubDir, recursive);
}
}
}
Использование:
CopyDirectory("C:\\MyData", "D:\\Backup\\MyData", recursive: true);
Эта функция создаёт структуру директорий и копирует все файлы, включая содержимое подпапок.
Разбираем код и нюансы
Мы сначала создаём директорию назначения (если её нет) — иначе при попытке скопировать туда файл получим ошибку. Внутри каждого цикла используем Path.GetFileName, чтобы не потерять имя файла или папки при составлении нового пути.
Кстати, если скопировать директорию саму в себя или в свою подпапку, получите эпичную рекурсию… и StackOverflowException. Не копируйте "C:\\Data" в "C:\\Data\\Backup". Компьютер обидится!
Копирование только файлов (без подпапок)
Иногда достаточно скопировать только файлы верхнего уровня (без погружения в подпапки):
void CopyFilesOnly(string sourceDir, string destDir)
{
if (!Directory.Exists(destDir))
Directory.CreateDirectory(destDir);
foreach (string filePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(filePath);
string destFilePath = Path.Combine(destDir, fileName);
File.Copy(filePath, destFilePath, overwrite: true);
}
}
Пример — реализуем резервное копирование
Добавим этот функционал в наше «Домашнее приложение», которое мы развиваем по всему курсу. Пусть оно теперь умеет делать резервную копию своих данных.
using System;
using System.IO;
namespace HomeApp
{
class Program
{
static void CopyDirectory(string sourceDir, string destDir, bool recursive)
{
if (!Directory.Exists(sourceDir))
throw new DirectoryNotFoundException($"Исходная папка не найдена: {sourceDir}");
if (!Directory.Exists(destDir))
Directory.CreateDirectory(destDir);
foreach (string filePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(filePath);
string destFilePath = Path.Combine(destDir, fileName);
File.Copy(filePath, destFilePath, overwrite: true);
}
if (recursive)
{
foreach (string dirPath in Directory.GetDirectories(sourceDir))
{
string dirName = Path.GetFileName(dirPath);
string destSubDir = Path.Combine(destDir, dirName);
CopyDirectory(dirPath, destSubDir, recursive);
}
}
}
static void Main(string[] args)
{
Console.WriteLine("Введите путь к рабочей директории:");
string source = Console.ReadLine()!;
Console.WriteLine("Введите путь к папке для резервной копии:");
string dest = Console.ReadLine()!;
try
{
CopyDirectory(source, dest, recursive: true);
Console.WriteLine("Резервная копия успешно создана!");
}
catch (Exception ex)
{
Console.WriteLine("Ошибка при копировании: " + ex.Message);
}
}
}
}
3. Полезные нюансы
Сравнение: файл vs директория — таблица
| Файл | Директория | |
|---|---|---|
| Встроенный метод | |
- |
| Объектно-ориентированный | |
- |
| Обход вложенности | Не требуется | Требуется рекурсия |
| Создание структуры | Автоматически | Нужно вручную создавать вложенность |
| Опасность рекурсии | Нет | Есть — не копировать «само в себя» |
Практическое применение и задачи для самостоятельной работы
Задачи копирования файлов и папок повсеместны: от резервного копирования до миграции данных или обновления ресурсов в игровых лаунчерах. Чаще всего подобные процедуры автоматизируют, чтобы не прибегать к ручной работе и не делать «ой, забыл файл!» в понедельник утром.
Копирование директорий востребовано во всех сценариях, где требуется сохранить не только содержимое, но и «каркас» структуры, включая подпапки, вложенные файлы, настройки.
Чеклист для копирования (чтобы не попасть впросак)
- Существование источника
- Существование папки назначения (создаём при необходимости через Directory.CreateDirectory)
- Сценарий перезаписи файлов — нужен ли overwrite: true?
- Не копируете ли вы папку «саму в себя»?
- Не превышаете ли длину пути (особенно на Windows)?
- Учитываете ли скрытые/системные файлы?
4. Особенности и типичные ошибки копирования
Права доступа
Копирование может завершиться неудачей, если у вашей программы нет прав на чтение файлов-источников или запись в директорию назначения. В таком случае вы получите одну из разновидностей UnauthorizedAccessException. Решение — запускать программу от имени администратора (только если это действительно нужно!) или выбрать нормальные папки для копирования.
Занятость файлов
Если файл открыт в другой программе (например, Excel держит его на замке), File.Copy может выбросить исключение. Убедитесь, что приложение, блокирующее файл, не мешает процессу, либо реализуйте логику повторных попыток (try-catch с ретраями).
Перезапись файлов
Какую логику выбрать: переписывать файлы назначения или не трогать их, если они есть? Для резервного копирования обычно уместна перезапись (overwrite: true), а для дублирования шаблонов — нет (overwrite: false).
Копирование скрытых и системных файлов
По умолчанию методы, которые мы использовали (Directory.GetFiles), возвращают все файлы, в том числе скрытые и системные. Если нужно пропустить их, фильтруйте явно:
foreach (string filePath in Directory.GetFiles(sourceDir))
{
var attr = File.GetAttributes(filePath);
if ((attr & FileAttributes.Hidden) == FileAttributes.Hidden)
continue; // Пропускаем скрытые файлы
// Остальной код копирования
}
Ошибки длинных путей
Windows долгое время ограничивала длину пути примерно 260 символами. В современных версиях это ограничение можно снять, но если вы работаете со старыми системами, длинные пути могут быть проблемой.
Символические ссылки и «джанкшены»
В специфичных сценариях в директориях могут встречаться символические ссылки или «junctions». Обычные методы копирования могут копировать их как обычные папки или игнорировать. Для большинства учебных задач это неважно, но если работаете с системными каталогами — будьте внимательнее.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ