获取目录中存在的所有文件,是文件管理、批量处理、备份工具等场景里非常基础的需求。C# 的 System.IO 命名空间提供了几种不同的方法,最常用的就是 Directory.GetFiles。它接受一个目录路径作为参数,返回该目录下所有文件的完整路径字符串数组。下面先看一段最简单的调用代码:

using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:\Users\Public\Documents";
string[] files = Directory.GetFiles(path);
Console.WriteLine($"共找到 {files.Length} 个文件:");
foreach (string file in files)
{
Console.WriteLine(file);
}
}
}
这段代码假设目录存在且程序有访问权限,否则会抛出 DirectoryNotFoundException 或 UnauthorizedAccessException。GetFiles 默认只返回当前目录下的文件,不会进入子目录。如果需要递归包含所有子目录中的文件,可以传入第二个参数 SearchOption.AllDirectories,比如 Directory.GetFiles(path, "*", SearchOption.AllDirectories)。这里的星号是通配符,表示匹配所有文件名,也可以换成 *.txt、*.jpg 等具体模式。
递归枚举与通配符过滤
当目录结构比较深,一个目录里可能还有多层子目录时,就需要递归获取全部文件。Directory.GetFiles 的 SearchOption 枚举有两个值:TopDirectoryOnly 和 AllDirectories。前者是默认行为,后者会遍历所有嵌套子目录。比如要统计整个项目文件夹里的所有 .cs 文件,可以这样写:
string root = @"D:\Projects\MyApp";
string[] csFiles = Directory.GetFiles(root, "*.cs", SearchOption.AllDirectories);
foreach (string file in csFiles)
{
Console.WriteLine(file);
}
通配符匹配规则和 Windows 文件系统一致,星号代表零个或多个字符,问号代表单个字符。但要注意,通配符匹配只针对文件名部分,不包括路径。如果目录权限不足,GetFiles 在遇到第一个不可访问的子目录时就会抛出异常,整个方法调用失败,不会返回已经遍历到的结果。这在处理系统目录或者用户目录时很常见,因此后面会专门讨论异常处理和替代方案。
此外,通配符还可以组合使用,例如 *report?.xlsx 会匹配 report1.xlsx、reportA.xlsx 等,但不会匹配 report10.xlsx,因为问号只对应一个字符。如果希望忽略文件扩展名大小写,可以在比较时使用 StringComparison.OrdinalIgnoreCase,不过 GetFiles 在 Windows 上默认不区分大小写,因为 Windows 文件系统本身不区分大小写。
EnumerateFiles 的延迟加载特性
Directory.GetFiles 会一次性把整个目录的文件路径都加载到内存中,然后返回一个数组。对于包含几十万个文件的目录,这种做法可能会占用大量内存,甚至导致程序卡顿。.NET 4.0 引入了 Directory.EnumerateFiles,它返回一个 IEnumerable<string>,采用延迟执行机制,遍历一个获取一个,而不是一次性全部加载。
下面这段代码演示了 EnumerateFiles 的用法:
IEnumerable<string> files = Directory.EnumerateFiles(@"C:\LargeFolder", "*", SearchOption.AllDirectories);
long count = 0;
foreach (string file in files)
{
// 逐个处理文件,内存占用更低
count++;
if (count % 10000 == 0)
{
Console.WriteLine($"已处理 {count} 个文件...");
}
}
Console.WriteLine($"共处理 {count} 个文件");
EnumerateFiles 和 GetFiles 的签名几乎一样,也支持通配符和 SearchOption。区别在于 EnumerateFiles 在迭代过程中如果遇到权限不足的目录,同样会抛出异常。但是因为它推迟了获取时间,你可以在处理前先判断目录大小,或者配合 try-catch 跳过个别不可访问的目录。对于大目录遍历,通常建议优先使用 EnumerateFiles,因为它能显著降低内存峰值。
另一个差异是,EnumerateFiles 返回的枚举器底层使用 Windows API 的 FindFirstFile/FindNextFile 机制,因此在迭代过程中如果目录内容发生变化,可能会出现不一致的结果,这是文件系统遍历的固有问题。GetFiles 则先完成快照再返回数组,相对更稳定。
异常处理与安全遍历策略
遍历文件系统时,最常见的异常包括 DirectoryNotFoundException(目录不存在)、UnauthorizedAccessException(没有访问权限)、PathTooLongException(路径超过系统限制)以及 IOException(其他 I/O 错误)。下面是一个带异常处理的示例:
string path = @"C:\Windows\System32";
try
{
string[] files = Directory.GetFiles(path, "*.dll", SearchOption.TopDirectoryOnly);
foreach (string file in files)
{
Console.WriteLine(Path.GetFileName(file));
}
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("指定的目录不存在。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("没有权限访问该目录。");
}
catch (PathTooLongException)
{
Console.WriteLine("路径过长,无法访问。");
}
catch (IOException ex)
{
Console.WriteLine($"发生 I/O 错误:{ex.Message}");
}
如果只是需要跳过无法访问的目录继续遍历其他部分,可以结合 Directory.EnumerateDirectories 和递归方法自行实现,但要注意防止无限递归和重复访问。一个常见做法是使用 Stack<string> 或 Queue<string> 来手动管理待处理目录。下面的代码演示了一种安全的递归遍历方式:
using System;
using System.Collections.Generic;
using System.IO;
class SafeFileWalker
{
public static IEnumerable<string> SafeEnumerateFiles(string root, string pattern = "*")
{
var pending = new Stack<string>();
pending.Push(root);
while (pending.Count > 0)
{
string currentDir = pending.Pop();
string[] files = null;
string[] subDirs = null;
try
{
files = Directory.GetFiles(currentDir, pattern);
subDirs = Directory.GetDirectories(currentDir);
}
catch (UnauthorizedAccessException)
{
// 跳过无权限的目录
continue;
}
catch (DirectoryNotFoundException)
{
continue;
}
foreach (string file in files)
{
yield return file;
}
foreach (string dir in subDirs)
{
pending.Push(dir);
}
}
}
}
这段代码使用迭代器和栈结构手动控制遍历,遇到无权限目录直接跳过,不会中断整个枚举过程。需要注意的是 yield return 在异常处理块外部使用,这样可以避免编译错误。这种方案适合处理包含大量系统目录或受保护目录的场景。
获取文件的其他属性与替代方案
有时不仅需要文件路径,还需要文件大小、修改时间等信息。这时可以使用 DirectoryInfo 和 FileInfo 类。DirectoryInfo.GetFiles 返回 FileInfo 对象数组,每个 FileInfo 包含 Length、LastWriteTime、Extension 等属性。例如:
DirectoryInfo dirInfo = new DirectoryInfo(@"C:\Users\Public\Documents");
FileInfo[] fileInfos = dirInfo.GetFiles("*.pdf", SearchOption.TopDirectoryOnly);
foreach (FileInfo fi in fileInfos)
{
Console.WriteLine($"{fi.Name} - {fi.Length} bytes - 修改时间:{fi.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
}
DirectoryInfo.EnumerateFiles 同样提供了延迟加载的版本,返回 IEnumerable<FileInfo>。FileInfo 的 Length 属性在访问时会读取文件系统元数据,如果文件已被删除或移动,可能会抛出 FileNotFoundException。因此如果你的需求只是枚举文件名,建议使用字符串版本;如果需要详细的文件信息,再使用 FileInfo 版本。
除了 System.IO,.NET 还提供了 System.IO.Abstractions 这类第三方库来提升可测试性,或者使用 Microsoft.VisualBasic.FileIO.FileSystem 中的 GetFiles 方法,但基本思想相同。在实际项目中,如果目录很大且只需要处理一部分文件,可以结合 LINQ 的 Take 和 Skip 进行分页,不过要注意 EnumerateFiles 的延迟特性会保留文件系统句柄,直到枚举结束。
总体来看,C# 获取目录所有文件并不复杂,核心就是 Directory.GetFiles 和 Directory.EnumerateFiles 两个方法。理解它们的同步加载与延迟加载差异、通配符规则以及异常处理策略,能帮你写出更健壮的文件处理代码。
C#获取目录所有文件Directory.GetFiles修改时间:2026-10-02 07:08:44