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 目录 — 表格
| 文件 | 目录 | |
|---|---|---|
| 内置方法 | |
- |
| 面向对象 | |
- |
| 遍历嵌套 | 无需 | 需要递归 |
| 创建结构 | 自动 | 需要手动创建目录结构 |
| 递归风险 | 没有 | 有 — 不要把目录复制到它自己内部 |
实际应用和自学任务
文件和文件夹复制的任务很常见:从备份到数据迁移,或者游戏启动器里更新资源。这样的流程通常会自动化,避免手工操作时“哎,忘了一个文件”的尴尬。
目录复制适用于那些不仅要保存内容,还要保留“骨架”结构的场景,包括子文件夹、嵌套文件和配置。
复制清单(别踩雷)
- 源是否存在
- 目标文件夹是否存在(必要时用 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。普通复制方法可能会把它们当普通目录复制,或者忽略。对大多数学习场景来说这不重要,但如果你在处理系统目录,要特别小心。
GO TO FULL VERSION