路径与目录操作
本教程共 100 篇 · 第 94 篇 · 更新于 2026-07-31 · 约 8 分钟阅读
94. 路径与目录操作
本节目标:学完能用 Path 写出跨平台的文件路径,并用 Directory 创建、删除、列举目录。
操作文件和目录,第一步是「正确地描述一个路径」。新手最容易犯的错,就是手动拼字符串 "data\\logs\\app.log",换到 Linux 上立刻出错。C# 的 System.IO.Path 和 Directory 就是为解决这些麻烦设计的。
一、Path:永远别手拼分隔符
不同系统的目录分隔符不同:Windows 用 \,Linux/macOS 用 /。Path.Combine 会按当前系统自动选分隔符,这是写路径的唯一正确姿势。
namespace CSharpDemo;
string dir = Path.Combine("data", "logs");
string file = Path.Combine(dir, "app.log");
Console.WriteLine(file); // Windows: data\logs\app.log
Console.WriteLine(Path.GetFileName(file)); // app.log
Console.WriteLine(Path.GetExtension(file)); // .log
Console.WriteLine(Path.GetDirectoryName(file)); // data\logs
Tip
Path是静态类,方法都不碰磁盘,只处理「字符串」。所以拼路径不会报错,真正访问文件才可能被拒绝。获取当前系统分隔符可用Path.DirectorySeparatorChar。
二、从路径里取信息
Path 还能帮你拆出文件名、扩展名、不带扩展名的名字,省得自己写字符串截取:
namespace CSharpDemo;
string p = Path.Combine("temp", "report.csv");
Console.WriteLine(Path.GetFileNameWithoutExtension(p)); // report
Console.WriteLine(Path.GetPathRoot(p)); // 在 Linux 下为 /
Console.WriteLine(Path.ChangeExtension(p, ".txt")); // temp/report.txt
Note
Path的方法都是「纯字符串运算」,不管那个文件是否真存在。它们不会抛「文件找不到」,只有当你真的用Directory/File去碰磁盘时才可能出错。所以拼路径阶段可以放心写。
三、Directory:创建与删除
Directory 是静态类,方法直接作用于磁盘。创建目录时即使父目录不存在也会一并建好,且目录已存在也不会报错——对初始化场景很友好。
namespace CSharpDemo;
// 创建(递归建好 data\logs)
Directory.CreateDirectory(Path.Combine("data", "logs"));
if (Directory.Exists("data"))
{
Console.WriteLine("data 目录存在");
}
// 删除:recursive 为 true 才连子目录一起删
Directory.Delete("data", recursive: true);
Note
Directory.Delete不写recursive: true时,若目录非空会抛异常。删之前想清楚是不是真要连子内容一起清掉。
四、列举目录里的文件
两个常用方法:GetFiles 一次返回全部路径数组;EnumerateFiles 用迭代器逐条给出,处理上百万文件时更省内存。
namespace CSharpDemo;
Directory.CreateDirectory("data");
// 列出 data 下所有 .txt,可加 SearchOption.AllDirectories 搜子目录
foreach (var f in Directory.EnumerateFiles("data", "*.txt"))
{
Console.WriteLine(f);
}
五、DirectoryInfo:面向实例的写法
如果你想反复操作同一个目录,DirectoryInfo 比静态 Directory 更顺手:它把目录封装成对象,可读取创建时间、大小等属性。
namespace CSharpDemo;
var info = new DirectoryInfo("data");
if (!info.Exists)
{
info.Create();
}
Console.WriteLine($"创建于:{info.CreationTime}");
foreach (var fi in info.GetFiles())
{
Console.WriteLine($"{fi.Name} 占用 {fi.Length} 字节");
}
Tip只做一次操作用静态
Directory就够了;要多次访问同一个目录的元信息,用DirectoryInfo更清晰,也少拼路径。
六、新手踩坑
- 硬编码
\\或/分隔符,程序失去跨平台能力。一律用Path.Combine。 - 用相对路径时搞不清「当前目录」在哪。调试阶段先用绝对路径或打印
Directory.GetCurrentDirectory()。 - 删除目录忘了
recursive: true,非空目录删不动。但反过来,递归删除很危险,先确认路径。 - 大目录用
GetFiles一次性全加载,内存暴涨。换成EnumerateFiles惰性遍历。
Warning
Directory.Delete(path, recursive: true)是「不可逆的硬删除」,不会进回收站。生产代码删目录前,务必先用Directory.Exists确认路径没错,最好只删你自己创建的子目录,别拿到根目录或用户家目录去试。一个手滑,重要数据就没了。
补充:Path 常用方法速查
下面这些是最常碰到的 Path 方法,记住它们就够了:
Path.Combine(...):跨平台拼接路径,日常最常用。Path.GetFileName(path):取文件名(含扩展名)。Path.GetDirectoryName(path):取文件所在的目录部分。Path.GetExtension(path):取扩展名(含点,如.log)。Path.GetFileNameWithoutExtension(path):取文件名并去掉扩展名。Path.ChangeExtension(path, ".txt"):换一个扩展名。Path.GetFullPath(path):把相对路径解析成绝对路径,看清它到底指哪。Path.GetTempPath():取系统的临时目录,适合放用完即弃的临时文件。Path.DirectorySeparatorChar:当前系统的目录分隔符,需要时直接读它。
Note再强调一次:
Path的方法全是「纯字符串运算」,不碰磁盘。所以你可以用它大胆拼、大胆拆,它们不会因「文件不存在」而抛异常。真正的 IO 异常,只在你用Directory/File去访问磁盘时才可能发生。
补充:Directory 常用方法速查
目录操作同样有固定套路,常用方法一览:
Directory.CreateDirectory(path):创建目录(含缺失的父目录),已存在也不报错。Directory.Exists(path):判断目录是否存在,删之前先确认。Directory.Delete(path, recursive):recursive: true才连子内容一起删。Directory.GetFiles(path, "*.txt"):返回某目录下匹配的文件路径数组。Directory.EnumerateFiles(path):惰性枚举文件,处理大目录更省内存。Directory.GetDirectories(path):列出子目录路径。Directory.GetCurrentDirectory():取当前工作目录,调试路径问题时很有用。
相对路径还是绝对路径
刚上手时最容易被「相对路径相对谁」绊倒。在 dotnet run 的项目里,相对路径通常相对于「当前工作目录」(一般是你执行命令所在的目录),而不是 .cs 源码所在的目录。这就是「本地跑得好好的,发布后却找不到文件」的常见根源。
经验法则:调试阶段先用 Path.GetFullPath(相对路径) 把真实绝对路径打印出来,确认它指向你以为的位置;正式代码里,需要稳定定位的文件(配置、模板、资源),建议拼成基于程序所在目录或已知根目录的绝对路径,别被「当前目录」的漂移坑到。
Tip想拿到程序所在目录,可用
AppContext.BaseDirectory;它指向程序集加载的位置,比「当前工作目录」更稳,适合放你的配置文件和资源文件。相对路径适合临时草稿,绝对路径适合正式部署。
Note路径字符串里若混入了用户输入,要警惕「目录遍历」风险:用户传入
../../可能逃出你预期的目录、去动别的文件。生产代码在处理用户提供的文件名时,应校验最终GetFullPath是否仍落在允许的根目录内,避免越权访问。
七、为什么这样设计
为什么要有 Path 这么个「不碰磁盘只拼字符串」的类?因为路径本质上是字符串,但它有一套跨平台的规则:分隔符不同、根路径写法不同、各段能不能有空格不同。把这些规则集中到一个类里,所有代码都通过 Path.Combine 拼路径,就能一处写对、处处跑通,不必在每个拼字符串的地方都操心平台差异。
Directory 和 DirectoryInfo 则是「静态便利」与「实例对象」两种风格的并存:前者适合一次性动作,调用即走;后者把目录封装成对象,适合你要反复查询它的属性、在多处引用同一个目录。两者底层一致,只是用法的顺手程度不同。
八、Path / Directory / DirectoryInfo 怎么选
| 任务 | 首选 |
|---|---|
| 拼路径、取文件名/扩展名 | Path(纯字符串,不碰磁盘) |
| 创建/删除/判断目录存在(一次性) | Directory 静态方法 |
| 反复操作同一目录、读创建时间等元信息 | DirectoryInfo |
| 遍历大目录 | EnumerateFiles/EnumerateDirectories 惰性枚举 |
九、本节小结
路径交给 Path.Combine 保证跨平台,目录的增删查交给 Directory(一次性)或 DirectoryInfo(反复操作)。下章我们把文件真正读出来、写进去。