CodeGym /Các khóa học /C# SELF /Sao chép tệp và thư mục

Sao chép tệp và thư mục

C# SELF
Mức độ , Bài học
Có sẵn

1. Sao chép tệp

Cho đến giờ các thao tác với hệ thống tệp của chúng ta giống kiểu "đấm một phát": tạo tệp, đọc, xóa — xong. Nhưng trong thực tế thường gặp bài toán sao chép nội dung: backup tài liệu, nhân bản mẫu, tự động hóa xử lý dữ liệu. Sao chép có vẻ đơn giản (Ctrl+C và Ctrl+V — ai cũng biết), nhưng thực tế có nhiều điểm cần lưu ý.

Hôm nay mình sẽ đi qua mọi cách sao chép tệp và thư mục — từ đơn giản nhất đến hơi phức tạp hơn. Cùng xem các lớp .NET hỗ trợ như thế nào, giới hạn ra sao, lỗi có thể gặp và phải làm gì khi thư mục nguồn có tới vài ngàn thư mục con, vài triệu tệp.

Class File và method Copy

Cách đơn giản nhất để sao chép tệp là dùng phương thức tĩnh File.Copy. Phương thức này nhận đường dẫn tới tệp nguồn, đường dẫn tới tệp đích và tham số tuỳ chọn: có cho phép ghi đè hay không nếu tệp đã tồn tại.

using System.IO;

// Ví dụ đơn giản sao chép tệp
File.Copy("source.txt", "destination.txt");

Nếu tệp đích đã tồn tại, phương thức sẽ ném ngoại lệ. Để cho phép ghi đè rõ ràng, dùng tham số thứ ba:

File.Copy("source.txt", "destination.txt", overwrite: true);

Lưu ý quan trọng: Nếu tham số thứ hai ("destination.txt") là đường dẫn tới một thư mục tồn tại chứ không phải tệp, sẽ xảy ra lỗi. Phương thức mong đợi đường dẫn tới tệp!

Làm việc với đường dẫn

Như mọi khi, đừng quên dùng Path.Combine để tránh rắc rối do slash kép hay slash ngược:

string sourcePath = Path.Combine("Data", "input.txt");
string destPath = Path.Combine("Backup", "input_backup.txt");

File.Copy(sourcePath, destPath, overwrite: true);

Xử lý lỗi

Chuyện gì có thể sai khi sao chép tệp? Nhiều thứ: tệp có thể đang bị process khác giữ, tệp nguồn không tồn tại, bạn thiếu quyền, ổ đĩa đích đầy. Dùng try/catch để xử lý:

try
{
    File.Copy("bigdata.txt", "bigdata_backup.txt", overwrite: false);
    Console.WriteLine("Tệp đã được sao chép thành công!");
}
catch (IOException ex)
{
    Console.WriteLine($"Lỗi I/O: {ex.Message}");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Không có quyền truy cập tới tệp hoặc thư mục.");
}
catch (Exception ex)
{
    Console.WriteLine($"Lỗi khác: {ex.Message}");
}

Class FileInfo và method CopyTo

Nếu bạn đã có đối tượng FileInfo, có thể gọi CopyTo trên nó:

var fi = new FileInfo("report.xlsx");
fi.CopyTo("backup_report.xlsx");

Tham số thứ ba (overwrite) trong CopyTo chỉ xuất hiện từ .NET Core 2.0+, nên nếu bạn code trên framework cũ hơn — đừng ngạc nhiên khi gặp lỗi.

Sao chép tệp "vào hư không" (hoặc "không bị bắt — không phải trộm")

Hãy cẩn thận với đường dẫn đích. Nếu thư mục đích không tồn tại, .NET sẽ ném lỗi. Vì vậy trước khi sao chép tệp nên đảm bảo thư mục nhận tồn tại:

string backupDir = "Backup";
if (!Directory.Exists(backupDir))
{
    Directory.CreateDirectory(backupDir);
}

string targetPath = Path.Combine(backupDir, "mydoc.txt");
File.Copy("mydoc.txt", targetPath);

2. Sao chép thư mục: không phải chuyện dành cho người yếu tim

Đây chính là lúc các cuộc phiêu lưu bắt đầu! Trong .NET không có method "kỳ diệu" Directory.Copy làm hết mọi thứ cho bạn bằng một dòng (như class File). Phải làm mấy bước thủ công một chút và viết hàm để sao chép đệ quy tất cả tệp và thư mục con.

Tại sao không có Directory.Copy?

Sao chép thư mục không luôn đơn giản. Cần cân nhắc mỗi thư mục có thể chứa tệp, thư mục con, tệp ẩn, tệp với quyền đặc biệt và đường dẫn dài. Nên các dev .NET để cho lập trình viên quyết định cần sao chép gì. Nhưng mình không tìm đường tắt — sẽ viết hàm của riêng ta!

Ví dụ sao chép thư mục đệ quy

Bài toán: sao chép nội dung một thư mục (và toàn bộ "vũ trụ" con của nó) sang thư mục khác.

using System;
using System.IO;

void CopyDirectory(string sourceDir, string destDir, bool recursive)
{
    // Kiểm tra xem thư mục nguồn có tồn tại không
    if (!Directory.Exists(sourceDir))
        throw new DirectoryNotFoundException($"Thư mục nguồn không tìm thấy: {sourceDir}");

    // Tạo thư mục đích nếu chưa có
    if (!Directory.Exists(destDir))
        Directory.CreateDirectory(destDir);

    // Sao chép tất cả tệp
    foreach (string filePath in Directory.GetFiles(sourceDir))
    {
        string fileName = Path.GetFileName(filePath);
        string destFilePath = Path.Combine(destDir, fileName);
        File.Copy(filePath, destFilePath, overwrite: true);
    }

    // Nếu là đệ quy — sao chép tất cả thư mục con
    if (recursive)
    {
        foreach (string dirPath in Directory.GetDirectories(sourceDir))
        {
            string dirName = Path.GetFileName(dirPath);
            string destSubDir = Path.Combine(destDir, dirName);
            // Gọi đệ quy!
            CopyDirectory(dirPath, destSubDir, recursive);
        }
    }
}

Sử dụng:

CopyDirectory("C:\\MyData", "D:\\Backup\\MyData", recursive: true);

Hàm này tạo cấu trúc thư mục và sao chép mọi tệp, kể cả nội dung trong các thư mục con.

Giải mã code và các điểm cần lưu ý

Mình tạo thư mục đích trước (nếu chưa có) — nếu không khi cố sao chép tệp vào đó sẽ bị lỗi. Trong mỗi vòng lặp dùng Path.GetFileName để không mất tên tệp hoặc thư mục khi ghép đường dẫn mới.

À này, nếu bạn sao chép một thư mục vào chính nó hoặc vào thư mục con của nó, bạn sẽ gặp đệ quy vô hạn… và nhận StackOverflowException. Đừng sao chép "C:\\Data" vào "C:\\Data\\Backup". Máy tính sẽ "buồn" đấy!

Sao chép chỉ tệp (không sao chép thư mục con)

Đôi khi chỉ cần sao chép tệp ở cấp trên cùng (không lặn xuống thư mục con):

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);
    }
}

Ví dụ — triển khai tính năng backup

Thêm chức năng này vào "ứng dụng gia đình" mà chúng ta phát triển suốt khóa học. Giờ nó có thể tạo bản sao lưu dữ liệu của chính nó.

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($"Thư mục nguồn không tìm thấy: {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("Nhập đường dẫn tới thư mục làm việc:");
            string source = Console.ReadLine()!;
            Console.WriteLine("Nhập đường dẫn tới thư mục để lưu bản sao lưu:");
            string dest = Console.ReadLine()!;

            try
            {
                CopyDirectory(source, dest, recursive: true);
                Console.WriteLine("Sao lưu được tạo thành công!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Lỗi khi sao chép: " + ex.Message);
            }
        }
    }
}

3. Những điểm hữu ích cần biết

So sánh: tệp vs thư mục — bảng

Tệp Thư mục
Method tích hợp
File.Copy
-
Hướng đối tượng
FileInfo.CopyTo
-
Duyệt độ lồng Không cần Cần đệ quy
Tạo cấu trúc Tự động Cần tạo thủ công
Nguy cơ đệ quy Không Có — đừng sao chép vào chính nó

Ứng dụng thực tế và bài tập tự luyện

Bài toán sao chép tệp và thư mục có mặt khắp nơi: từ backup tới di chuyển dữ liệu hay cập nhật resource trong launcher game. Thường quy trình này được tự động hóa để tránh thao tác thủ công và tình trạng "ôi quên tệp!" vào sáng thứ Hai.

Sao chép thư mục cần khi bạn muốn giữ không chỉ nội dung mà cả "khung" cấu trúc, gồm thư mục con, tệp lồng nhau, cấu hình.

Checklist khi sao chép (để không vấp ngã)

  • Thư mục nguồn tồn tại
  • Thư mục đích tồn tại (tạo khi cần bằng Directory.CreateDirectory)
  • Chính sách ghi đè tệp — có cần overwrite: true không?
  • Bạn có đang sao chép thư mục vào chính nó không?
  • Độ dài đường dẫn có vượt giới hạn (đặc biệt trên Windows) không?
  • Có tính tới tệp ẩn/hệ thống không?

4. Đặc điểm và lỗi thường gặp khi sao chép

Quyền truy cập

Sao chép có thể thất bại nếu chương trình của bạn không có quyền đọc tệp nguồn hoặc ghi vào thư mục đích. Trong trường hợp đó bạn sẽ gặp một dạng UnauthorizedAccessException. Giải pháp — chạy ứng dụng với quyền admin (chỉ khi cần thiết!) hoặc chọn thư mục thích hợp để sao chép.

Tệp đang bị chiếm

Nếu tệp đang mở bởi chương trình khác (ví dụ Excel khóa tệp), File.Copy có thể ném ngoại lệ. Đảm bảo ứng dụng đang giữ tệp không can thiệp, hoặc triển khai logic thử lại (một vòng try-catch với retry).

Ghi đè tệp

Chọn chính sách: ghi đè tệp đích hay giữ nguyên nếu đã tồn tại? Với backup thường chọn ghi đè (overwrite: true), còn nhân bản mẫu thì thường không (overwrite: false).

Sao chép tệp ẩn và hệ thống

Theo mặc định các method chúng ta dùng (Directory.GetFiles) trả về mọi tệp, bao gồm tệp ẩn và hệ thống. Nếu muốn bỏ qua, lọc rõ ràng:

foreach (string filePath in Directory.GetFiles(sourceDir))
{
    var attr = File.GetAttributes(filePath);
    if ((attr & FileAttributes.Hidden) == FileAttributes.Hidden)
        continue; // Bỏ qua tệp ẩn

    // Phần còn lại của mã sao chép
}

Lỗi do đường dẫn dài

Windows từng giới hạn độ dài đường dẫn khoảng 260 ký tự. Ở các phiên bản hiện đại giới hạn này có thể được tắt, nhưng nếu làm việc trên hệ thống cũ thì đường dẫn dài có thể gây ra vấn đề.

Symbolic links và junctions

Trong một số kịch bản đặc thù có thể gặp symbolic links hoặc junctions. Các phương thức sao chép thông thường có thể sao chép chúng như thư mục bình thường hoặc bỏ qua. Với hầu hết bài tập học tập thì không quan trọng nhiều, nhưng nếu thao tác trên thư mục hệ thống — cần cẩn thận.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sao chép thư mục đệ quy
Sao chép thư mục đệ quy
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION