JavaRush /Курси /C# SELF /Копіювання файлів і каталогів

Копіювання файлів і каталогів

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

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 каталог — таблиця

Файл Каталог
Вбудований метод
File.Copy
-
Об’єктно-орієнтований
FileInfo.CopyTo
-
Обхід вкладеності Не потрібен Потрібна рекурсія
Створення структури Автоматично Потрібно вручну створювати ієрархію
Небезпека рекурсії Ні Є — не копіювати «сам у себе»

Практичне застосування та завдання для самостійної роботи

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

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

Чекліст для копіювання (щоб не влипнути)

  • Наявність джерела
  • Наявність каталогу призначення (створюємо за потреби через 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»

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

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