导读:本期聚焦于创作的《如何在C#中列出目录下的所有文件和子目录?》,敬请观看详情。在C#中获取目录下的文件清单,最常用的两个方法是 Directory.GetFiles 和 Directory.EnumerateFiles。前者一次性返回字符串数组,适合目录规模小、需要随机访问的场景;后者采用惰性枚举,边遍历边返回,不会提前把所有路径加载到内存。除了这两个静态方法,DirectoryInfo 类还提供了面向对象的目录操作方式,可以同时获取 FileInfo 和 DirectoryInfo 实例,便于读取大小、修改时间等元数据。实际处理时还要考虑权限不足、符号链接、超大目录树等边界情况。本文将结合代码示例,从基础列出、递归遍历到性能优化和异常处理,完整梳理 C# 中列出目录和文件的几种实现路径,并指出每种方案的适用边界,避免在大型目录结构中出现内存或权限问题。

在C#中列出目录和文件并不是只有一个API那么简单。System.IO命名空间提供了Directory静态类和DirectoryInfo实例类两条路线,各自又有同步、异步、惰性枚举等不同方法。选对API可以显著降低内存峰值,选错则可能在数十万文件的目录下卡死进程。本文从最常用的几个方法入手,逐步展开递归遍历、结果排序、权限处理和性能对比。

如何在C#中列出目录下的所有文件和子目录?

基础方法:Directory.GetFiles 与 Directory.GetDirectories

Directory静态类提供了最直接的文件列举能力。GetFiles方法接受目录路径作为第一个参数,可选地传入搜索模式与搜索选项。如果不指定搜索选项,默认只检查当前目录,不会自动进入子目录。返回结果是一个字符串数组,每个元素是文件的完整路径,可以直接交给File.ReadAllText、File.Copy等方法使用。

这种一次性返回数组的方式有一个明显特征:当GetFiles执行完成时,文件列表已经完全加载到内存中。对于包含几千个文件的目录,这点内存开销可以忽略不计;但如果目标目录存在几十万甚至上百万个文件,数组本身加上每个字符串对象的开销会迅速累积,可能导致短暂的卡顿甚至内存压力。因此,当目录规模较大或者只需要逐个处理文件时,后续介绍的EnumerateFiles会更合适。

using System;
using System.IO;

class ListFilesBasic
{
    static void Main()
    {
        string path = @"C:\example\project";
        string[] files = Directory.GetFiles(path);
        Console.WriteLine("文件总数:" + files.Length);
        foreach (string file in files)
        {
            Console.WriteLine(file);
        }

        // 只列出 .cs 文件
        string[] csFiles = Directory.GetFiles(path, "*.cs", SearchOption.TopDirectoryOnly);
        string[] dirs = Directory.GetDirectories(path);
        foreach (string dir in dirs)
        {
            Console.WriteLine("目录:" + dir);
        }
    }
}

GetFiles的搜索模式支持星号和问号作为通配符,例如*.cs会匹配所有扩展名为cs的文件,file?.txt可以匹配file1.txt但不会匹配file10.txt。搜索模式不是正则表达式,不能使用方括号或点号进行更复杂的匹配。如果需要按正则规则筛选文件,可以先列出所有文件,再用Regex类进行二次过滤。

使用 DirectoryInfo 和 FileInfo 获取详细信息

Directory静态类适合只需要路径字符串的场景,但如果还需要读取文件大小、创建时间、修改时间、只读属性等元数据,则应该使用DirectoryInfo和FileInfo。DirectoryInfo的构造函数接收目录路径,之后可以反复调用GetFiles、GetDirectories等方法,避免每次调用都重复解析路径。

DirectoryInfo.GetFiles返回的是FileInfo对象数组。FileInfo封装了Length、LastWriteTime、CreationTime、Attributes等属性,这些属性在访问时才会触发对应的文件系统查询。如果对同一文件读取多个属性,FileInfo内部会缓存部分信息,但多个FileInfo对象之间没有批量查询的优化,因此在需要读取大量文件元数据时,仍需衡量性能。

using System;
using System.IO;
using System.Linq;

class ListWithDirectoryInfo
{
    static void Main()
    {
        DirectoryInfo di = new DirectoryInfo(@"C:\example\project");
        FileInfo[] files = di.GetFiles("*.cs", SearchOption.TopDirectoryOnly);

        // 按修改时间倒序排列
        var sorted = files.OrderByDescending(f => f.LastWriteTime);
        foreach (FileInfo fi in sorted)
        {
            Console.WriteLine($"{fi.Name} - {fi.Length} 字节 - {fi.LastWriteTime}");
        }
    }
}

与Directory.GetFiles类似,DirectoryInfo.GetFiles同样支持SearchOption.AllDirectories实现递归列出。但GetFiles返回数组的特性意味着递归模式会在方法返回前访问完全部子目录,并把所有FileInfo对象装入内存。如果只需要按修改时间排序,可以先用DirectoryInfo.EnumerateFiles获得IEnumerable<FileInfo>,再用OrderByDescending处理,但要注意排序本身仍然需要缓存全部元素,因此惰性枚举在排序场景下的内存优势会被排序操作抵消。

递归遍历目录树与 EnumerateFiles 的惰性枚举

EnumerateFiles是解决大目录内存问题的关键。它与GetFiles使用相同的参数签名,但返回类型是IEnumerable<string>。这个接口本身不会立即执行枚举,只有在foreach开始迭代时,才会逐步访问文件系统并返回一个文件路径。这意味着可以在处理完前100个文件后提前退出,而不必等待剩余文件被读取。

除了提前退出,惰性枚举还允许边遍历边处理。例如从日志目录中读取每个文件并解析内容,如果使用GetFiles,必须先拿到全部文件路径再循环;而EnumerateFiles可以在拿到第一个路径时就进入处理逻辑,降低启动延迟。面对千万级文件目录时,这种差异会变得非常明显。

using System;
using System.IO;
using System.Collections.Generic;

class LazyEnumeration
{
    static void Main()
    {
        string root = @"C:\example\project";
        IEnumerable<string> allFiles = Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories);
        int count = 0;
        foreach (string file in allFiles)
        {
            Console.WriteLine(file);
            count++;
            if (count > 100)
            {
                Console.WriteLine("已显示前100项,提前退出");
                break;
            }
        }
    }
}

SearchOption.AllDirectories虽然方便,但它会递归所有层级的子目录。如果目录树中存在指向父目录的符号链接或Junction,可能造成无限循环。Windows的符号链接默认不会在SearchOption.AllDirectories中被自动跳过,因此在处理用户提供的任意目录时,最好自己实现递归逻辑,并加入深度限制或循环检测。

using System;
using System.IO;
using System.Collections.Generic;

class SafeRecursiveLister
{
    public static IEnumerable<string> SafeEnumerateFiles(string root, string pattern = "*")
    {
        IEnumerable<string> files;
        try
        {
            files = Directory.EnumerateFiles(root, pattern, SearchOption.TopDirectoryOnly);
        }
        catch (UnauthorizedAccessException)
        {
            yield break;
        }
        catch (DirectoryNotFoundException)
        {
            yield break;
        }

        foreach (string file in files)
        {
            yield return file;
        }

        IEnumerable<string> dirs;
        try
        {
            dirs = Directory.EnumerateDirectories(root, "*", SearchOption.TopDirectoryOnly);
        }
        catch (UnauthorizedAccessException)
        {
            yield break;
        }
        catch (DirectoryNotFoundException)
        {
            yield break;
        }

        foreach (string dir in dirs)
        {
            foreach (string nestedFile in SafeEnumerateFiles(dir, pattern))
            {
                yield return nestedFile;
            }
        }
    }

    static void Main()
    {
        foreach (string f in SafeEnumerateFiles(@"C:\example\project"))
        {
            Console.WriteLine(f);
        }
    }
}

上面的SafeEnumerateFiles使用yield return构建了一个安全的惰性枚举器。它在访问子目录时单独捕获UnauthorizedAccessException和DirectoryNotFoundException,跳过无权限或不存在的目录,而不是中断整个遍历。实际项目中可以在此基础上增加深度计数器,当递归深度超过某个阈值时停止继续深入,有效防止目录环路。

权限处理、异常与性能对比

列出目录时最常遇到的异常是UnauthorizedAccessException和DirectoryNotFoundException。前者出现在当前用户没有读取某个子目录权限时,例如访问C:\Windows\System32或C:\Program Files下的受保护目录。后者通常是因为目标目录在调用前被删除或移动。对于.NET Core 3.0及以上版本,可以使用EnumerationOptions来简化权限处理。

using System;
using System.IO;

class EnumerationOptionsDemo
{
    static void Main()
    {
        string root = @"C:\Windows\System32";
        var options = new EnumerationOptions
        {
            RecurseSubdirectories = true,
            IgnoreInaccessible = true,
            ReturnSpecialDirectories = false
        };

        foreach (string file in Directory.EnumerateFiles(root, "*.dll", options))
        {
            Console.WriteLine(file);
        }
    }
}

EnumerationOptions的IgnoreInaccessible属性设置为true后,枚举器会自动跳过无法访问的目录,不再抛出异常。RecurseSubdirectories等价于SearchOption.AllDirectories。这个选项在.NET Framework中不可用,旧项目只能手动捕获异常。ReturnSpecialDirectories为false表示不返回系统特殊目录,进一步减少干扰项。

方法返回类型内存占用适用场景
Directory.GetFilesstring[]高小目录、需要随机访问
Directory.EnumerateFilesIEnumerable<string>低大目录、流式处理
DirectoryInfo.GetFilesFileInfo[]高需要文件元数据
DirectoryInfo.EnumerateFilesIEnumerable<FileInfo>低需要元数据且目录大

从表格可以看出,只要目录可能包含大量文件,就应该优先选择Enumerate系列。如果需要一次性获取所有结果并多次随机访问,GetFiles返回数组会更加简单。实践中还可以结合任务并行库处理文件内容,但要注意并行读取同一个机械硬盘上的文件并不会带来线性加速,反而可能因磁头抖动降低性能。

C#列出目录DirectoryInfoEnumerateFiles修改时间:2026-09-26 18:14:53

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0926/62240.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。