CodeGym /课程 /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);
    }

    // 如果 recursive 为 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。普通复制方法可能会把它们当普通目录复制,或者忽略。对大多数学习场景来说这不重要,但如果你在处理系统目录,要特别小心。

2
任务
C# SELF, 第 40 级, 课程 2
已锁定
递归复制文件夹
递归复制文件夹
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION