Directory.EnumerateFiles 为什么比 GetFiles 更适合大目录
它返回
IEnumerable<string></string>而非一次性加载全部路径到内存,遍历时按需获取文件名,避免在含数万文件的目录中触发
OutOfMemoryException。尤其适合后续要过滤、投影或提前中断的场景。
但注意:枚举本身仍是同步 I/O,不等于“异步遍历”——它不会释放当前线程。真需要非阻塞,得配合
Task.Run或用
FileSystemWatcher+ 异步回调组合实现。 不要在 UI 线程直接调用它处理超大目录,否则界面卡死 路径通配符(如
"*.log")由 Windows API 原生支持,效率高;正则过滤必须靠 C# 侧
.Where(),会遍历全部再筛选 若需访问文件属性(大小、时间戳),
EnumerateFiles只返回路径,额外
FileInfo构造会引发重复系统调用——此时应改用
EnumerateFileSystemEntries配合
new FileInfo()批量缓存
如何安全处理权限不足或路径不存在的异常
Directory.EnumerateFiles在遇到拒绝访问(
UnauthorizedAccessException)或路径不存在(
DirectoryNotFoundException)时直接抛异常,不跳过。无法像 PowerShell 的
Get-ChildItem -ErrorAction SilentlyContinue那样静默忽略。
常见做法是封装一层可恢复的枚举器:
public static IEnumerable<string> SafeEnumerateFiles(string path, string searchPattern = "*.*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
try
{
return Directory.EnumerateFiles(path, searchPattern, searchOption);
}
catch (UnauthorizedAccessException)
{
yield break;
}
catch (DirectoryNotFoundException)
{
yield break;
}
}
不能用 try/catch包裹整个
foreach循环——异常发生在
MoveNext()内部,循环外捕获不到 子目录递归时,某一级失败会导致整个枚举终止;如需继续遍历兄弟目录,得手动实现目录树迭代(用
EnumerateDirectories+ 逐层
EnumerateFiles) 某些网络路径(如断开的 NAS)可能抛
IOException,建议一并 catch
配合 LINQ 实现延迟过滤与提前退出
利用其返回
IEnumerable的特性,把耗时操作(如正则匹配、字符串处理)和终止条件(如只取前 100 个)留在枚举过程中,避免无谓遍历:
var recentLogs = Directory.EnumerateFiles(@"C:\App\Logs", "*.log", SearchOption.AllDirectories)
.Where(p => File.GetLastWriteTime(p) > DateTime.Now.AddDays(-7))
.Take(100)
.ToArray(); // 此时才真正触发遍历
Take(n)后接
ToArray()或
ToList()是触发执行的关键,纯链式调用不干活
File.GetLastWriteTime(p)是同步磁盘 I/O,每调一次都可能慢;如果只是按名称过滤,优先用
searchPattern参数(如
"error_*.log")交由系统完成 不要在
Where中做复杂解析(如读文件头),那会把延迟优势完全抵消
想真正异步?得绕开 EnumerateFiles 自己调度
Directory.EnumerateFiles没有异步重载,.NET 6+ 的
FileSystemEnumerable也未暴露异步接口。所谓“异步枚举文件”,本质是把同步枚举扔进线程池:
var files = await Task.Run(() =>
Directory.EnumerateFiles(@"D:\Data", "*.csv")
.Where(f => f.Length > 1000)
.Take(50)
.ToArray());
这能释放调用线程(比如 ASP.NET Core 的请求线程),但**不减少总耗时,也不降低 I/O 压力**
高频调用时注意线程池饥饿——大量 Task.Run可能挤占 CPU 密集型任务资源 若目标是响应式流(如 Web API 分块返回文件列表),应考虑用
IAsyncEnumerable<string></string>手动包装,每次
yield return前加
await Task.Yield()让出控制权
真正的异步文件系统操作仍受限于 Windows API 和底层驱动,目前没有银弹。别被“异步枚举”字面误导——先厘清你要解的是阻塞问题,还是吞吐瓶颈,或是 UI 响应性。
