Directory.GetFiles支持SearchOption.AllDirectories直接递归搜索,无需手写递归;注意通配符格式(如"*.txt")、内存限制(大数据量改用EnumerateFiles)、符号链接跨平台差异及多后缀需手动过滤。

用 Directory.GetFiles 递归找指定后缀文件最直接
多数人卡在“递归”上,其实 Directory.GetFiles 自带 SearchOption.AllDirectories 参数,不用手写递归函数。它底层调用 Win32 API,比自己用 Directory.GetDirectories + 循环拼接路径更稳、更快,也自动跳过无权限目录(抛 UnauthorizedAccessException,但不会崩)。
常见错误是传错搜索模式——比如写 "*.log" 没问题,但写 "log" 或 "log*" 就匹配不到;后缀必须带 * 和点,且不区分大小写(Windows 默认行为)。
-
Directory.GetFiles(@"C:\logs", "*.txt", SearchOption.AllDirectories)返回所有.txt文件的完整路径数组 - 如果只要文件名不含路径,别用
Path.GetFileName在循环里反复调用,改用Select+Path.GetFileName(LINQ)或提前投影,避免重复字符串分配 - 注意:该方法会一次性加载全部路径到内存,超大目录(如数百万文件)可能 OOM;此时得换
Directory.EnumerateFiles
大目录必须用 Directory.EnumerateFiles 防止内存爆掉
EnumerateFiles 返回 IEnumerable<string></string>,边遍历边 yield,内存占用几乎恒定。适合日志归档、备份扫描等真实生产场景——你根本不知道目标目录有多大,也不能假设它能全塞进 RAM。
但它不捕获所有异常:遇到权限不足目录时默认跳过,不报错;想感知失败就得手动 try/catch 包裹每次迭代,或者改用 DirectoryInfo + 自定义遍历逻辑。
-
foreach (var file in Directory.EnumerateFiles(@"D:\data", "*.json", SearchOption.AllDirectories)) { /* 处理单个 file */ }—— 安全、低内存、可随时 break - 不能直接对返回结果调
.Length或.ToArray(),否则退化成GetFiles行为 - 和
GetFiles一样,搜索模式只支持简单通配符(*和?),不支持正则;要复杂过滤得靠Where后置筛选
跨平台要注意 SearchOption.AllDirectories 的符号链接行为
.NET 6+ 在 Linux/macOS 上默认不跟随符号链接(symlink),而 Windows 的 NTFS junction/soft link 行为也不统一。这意味着同一段代码,在 WSL 里可能漏掉挂载的日志卷,但在 Windows 原生环境又意外钻进不该进的目录。
没有跨平台通用开关,只能按需处理:Linux 下若需跟随链接,得自己用 FileSystemEnumerable 或第三方库(如 K4os.FileSystem);Windows 下建议先用 File.GetAttributes 过滤掉 ReparsePoint 类型路径。
- 检查是否为符号链接:
(File.GetAttributes(path) & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint -
EnumerateFiles和GetFiles都不提供“跳过链接”参数,这是设计缺失,不是你用错了 - CI/CD 流水线跑在 Docker 容器里时,宿主机挂载的路径很可能含符号链接,务必实测
过滤多个后缀别拼 "*.log|*.txt"——这语法根本不存在
C# 的文件搜索模式不支持管道分隔或多扩展名语法。写 "*.log|*.txt" 或 "*.{log,txt}" 会静默返回空数组,没有任何提示,非常隐蔽。
正确做法只有两个:一是多次调用分别查再合并(适合后缀少、目录小);二是用 EnumerateFiles + Path.GetExtension 手动判断(推荐,灵活且易读)。
- 错误示范:
Directory.GetFiles(root, "*.log|*.cfg")→ 返回空 - 正确写法:
Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories).Where(f => ".log.txt.yml".Contains(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase)) - 注意
Path.GetExtension返回包括点的字符串(如".log"),所以判断时要带点,别漏了
事情说清了就结束。真正麻烦的从来不是怎么写,而是目录权限、符号链接、内存边界和跨平台路径语义这些藏在表层 API 下面的东西。


















