C#读取文件夹和文件列表:全面指南
在 C# 开发中,经常需要获取文件夹中的文件列表或子文件夹结构,例如文件管理器、批量处理工具、备份程序等场景。本文将详细介绍 C# 中读取文件夹和文件列表的各种方法,包括基础操作、递归遍历、过滤搜索、高级属性获取等,帮助开发者根据实际需求选择最合适的实现方式。
一、基础方法:使用 Directory 类的静态方法
System.IO.Directory
类提供了一系列静态方法,可快速获取文件夹和文件列表,适合简单场景使用。
1. 获取指定目录下的所有文件
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:MyFolder";
try
{
// 获取指定目录下的所有文件(仅当前目录)
string[] files = Directory.GetFiles(path);
Console.WriteLine($"目录 {path} 中的文件:");
foreach (string file in files)
{
Console.WriteLine(file);
}
// 获取指定目录下的特定类型文件
string[] txtFiles = Directory.GetFiles(path, "*.txt");
Console.WriteLine($"n目录 {path} 中的txt文件:");
foreach (string file in txtFiles)
{
Console.WriteLine(file);
}
// 获取指定目录及子目录中的所有文件
string[] allFiles = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
Console.WriteLine($"n目录 {path} 及其子目录中的所有文件:");
foreach (string file in allFiles)
{
Console.WriteLine(file);
}
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("指定的目录不存在");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("没有访问该目录的权限");
}
catch (PathTooLongException)
{
Console.WriteLine("路径过长");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
}
2. 获取指定目录下的所有子文件夹
// 获取指定目录下的所有子文件夹(仅当前目录)
string[] directories = Directory.GetDirectories(path);
Console.WriteLine($"目录 {path} 中的子文件夹:");
foreach (string dir in directories)
{
Console.WriteLine(dir);
}
// 获取指定目录及所有子目录中的子文件夹
string[] allDirectories = Directory.GetDirectories(path, "*", SearchOption.AllDirectories);
Console.WriteLine($"n目录 {path} 及其子目录中的所有文件夹:");
foreach (string dir in allDirectories)
{
Console.WriteLine(dir);
}
Directory 类方法特点:
- 提供静态方法,无需创建实例,调用简单
GetFiles()
和GetDirectories()
方法一次性返回所有结果- 第三个参数
SearchOption
指定搜索范围:SearchOption.TopDirectoryOnly
:仅当前目录(默认)SearchOption.AllDirectories
:当前目录及所有子目录
二、面向对象方法:使用 DirectoryInfo 类
DirectoryInfo
类提供了面向对象的方式来操作目录,适合需要多次使用目录信息的场景,避免重复解析路径。
1. 基本使用方法
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:MyFolder";
DirectoryInfo dirInfo = new DirectoryInfo(path);
try
{
// 检查目录是否存在
if (!dirInfo.Exists)
{
Console.WriteLine("目录不存在");
return;
}
// 获取当前目录下的所有文件
FileInfo[] files = dirInfo.GetFiles();
Console.WriteLine($"目录 {path} 中的文件:");
foreach (FileInfo file in files)
{
Console.WriteLine($"{file.Name} (大小: {file.Length} 字节)");
}
// 获取当前目录下的所有子文件夹
DirectoryInfo[] subDirs = dirInfo.GetDirectories();
Console.WriteLine($"n目录 {path} 中的子文件夹:");
foreach (DirectoryInfo subDir in subDirs)
{
Console.WriteLine($"{subDir.Name} (创建时间: {subDir.CreationTime})");
}
// 获取特定类型文件(包括子目录)
FileInfo[] txtFiles = dirInfo.GetFiles("*.txt", SearchOption.AllDirectories);
Console.WriteLine($"n目录 {path} 及其子目录中的txt文件:");
foreach (FileInfo file in txtFiles)
{
Console.WriteLine($"{file.FullName} (修改时间: {file.LastWriteTime})");
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("没有访问该目录的权限");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
}
DirectoryInfo 与 Directory 的区别:
Directory
是静态类,DirectoryInfo
是实例类DirectoryInfo
的方法返回FileInfo
和DirectoryInfo
对象,包含更多文件 / 目录属性- 对于多次操作同一目录,
DirectoryInfo
性能更好(只需解析一次路径) DirectoryInfo
提供更多实例属性:Name
、FullName
、CreationTime
、LastAccessTime
等
三、枚举方法:延迟加载提高性能
.NET 4.0 及以上版本提供了EnumerateFiles()
和EnumerateDirectories()
方法,采用延迟加载(Lazy Loading)方式返回结果,适合处理包含大量文件的目录。
using System;
using System.IO;
class Program
{
static void Main()
{
string path = @"C:MyFolder";
try
{
// 枚举方式获取文件(延迟加载)
Console.WriteLine($"枚举目录 {path} 中的所有文件:");
int fileCount = 0;
foreach (string file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
{
Console.WriteLine(file);
fileCount++;
// 演示:仅显示前10个文件
if (fileCount >= 10)
{
Console.WriteLine("... 更多文件 ...");
break;
}
}
// 使用DirectoryInfo的枚举方法
DirectoryInfo dirInfo = new DirectoryInfo(path);
Console.WriteLine($"n枚举目录 {path} 中的所有子文件夹:");
int dirCount = 0;
foreach (DirectoryInfo dir in dirInfo.EnumerateDirectories("*", SearchOption.AllDirectories))
{
Console.WriteLine(dir.FullName);
dirCount++;
// 演示:仅显示前5个子文件夹
if (dirCount >= 5)
{
Console.WriteLine("... 更多文件夹 ...");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
}
枚举方法的优势:
- 无需一次性加载所有结果到内存,适合处理大量文件
- 可以在遍历过程中提前终止(如示例中的 break)
- 内存占用低,性能更好
- 支持流式处理,适合大型目录
四、递归获取所有文件和文件夹
在某些场景下,需要自定义递归逻辑来获取文件和文件夹列表,特别是需要在遍历过程中添加自定义过滤条件时。
1. 递归获取所有文件
using System;
using System.IO;
using System.Collections.Generic;
class FileLister
{
// 递归获取所有文件
static List<string> GetAllFiles(string path)
{
List<string> files = new List<string>();
try
{
// 添加当前目录的文件
files.AddRange(Directory.GetFiles(path));
// 递归处理子目录
string[] subDirs = Directory.GetDirectories(path);
foreach (string dir in subDirs)
{
files.AddRange(GetAllFiles(dir));
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"无法访问目录: {path},已跳过");
}
catch (PathTooLongException)
{
Console.WriteLine($"路径过长: {path},已跳过");
}
catch (IOException ex)
{
Console.WriteLine($"IO错误: {path} - {ex.Message},已跳过");
}
return files;
}
static void Main()
{
string rootPath = @"C:MyFolder";
Console.WriteLine($"开始递归获取 {rootPath} 下的所有文件...");
List<string> allFiles = GetAllFiles(rootPath);
Console.WriteLine($"n共找到 {allFiles.Count} 个文件:");
foreach (string file in allFiles)
{
Console.WriteLine(file);
}
}
}
2. 递归获取目录结构(树形展示)
using System;
using System.IO;
class DirectoryTree
{
// 递归显示目录结构
static void ShowDirectoryTree(string path, string indent = "")
{
try
{
// 获取当前目录的子目录
string[] subDirs = Directory.GetDirectories(path);
foreach (string dir in subDirs)
{
// 获取目录名称
string dirName = Path.GetFileName(dir);
// 显示目录
Console.WriteLine($"{indent}├─ {dirName}");
// 递归显示子目录,增加缩进
ShowDirectoryTree(dir, indent + "│ ");
}
// 显示当前目录的文件
string[] files = Directory.GetFiles(path);
for (int i = 0; i < files.Length; i++)
{
string fileName = Path.GetFileName(files[i]);
// 最后一个文件使用不同的连接线
string line = (i == files.Length - 1 && subDirs.Length == 0)
? $"{indent}└─ {fileName}"
: $"{indent}├─ {fileName}";
Console.WriteLine(line);
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"{indent}├─ 无法访问的目录(权限不足)");
}
catch (Exception ex)
{
Console.WriteLine($"{indent}├─ 错误: {ex.Message}");
}
}
static void Main()
{
string rootPath = @"C:MyFolder";
Console.WriteLine($"目录结构: {rootPath}");
ShowDirectoryTree(rootPath);
}
}
自定义递归的优势:
- 可以在递归过程中添加复杂的过滤逻辑
- 可以实现特殊的错误处理策略(如跳过无权限的目录)
- 可以构建自定义的数据结构保存文件信息
- 可以实现进度报告或中断机制
五、过滤与搜索技巧
实际开发中,经常需要根据特定条件过滤文件或文件夹,以下是一些常用的过滤方法。
1. 使用搜索模式
// 搜索模式示例
string path = @"C:MyFolder";
// 所有文本文件
var txtFiles = Directory.GetFiles(path, "*.txt");
// 所有图片文件(多种扩展名)
var imageFiles1 = Directory.GetFiles(path, "*.jpg")
.Concat(Directory.GetFiles(path, "*.jpeg"))
.Concat(Directory.GetFiles(path, "*.png"))
.Concat(Directory.GetFiles(path, "*.gif"));
// 名称以"report"开头的Excel文件
var reportFiles = Directory.GetFiles(path, "report*.xlsx", SearchOption.AllDirectories);
2. 使用 LINQ 过滤文件
using System;
using System.IO;
using System.Linq;
class FileFilter
{
static void Main()
{
string path = @"C:MyFolder";
// 使用LINQ过滤文件
var largeFiles = new DirectoryInfo(path)
.GetFiles("*.*", SearchOption.AllDirectories)
.Where(f => f.Length > 1024 * 1024) // 大于1MB的文件
.Where(f => f.LastWriteTime > DateTime.Now.AddDays(-30)) // 30天内修改过
.OrderByDescending(f => f.Length) // 按大小降序排列
.Take(10); // 取前10个
Console.WriteLine("10个最大的30天内修改过的文件:");
foreach (var file in largeFiles)
{
Console.WriteLine($"{file.FullName} - {file.Length / 1024} KB - {file.LastWriteTime}");
}
// 过滤特定属性的文件夹
var recentDirs = new DirectoryInfo(path)
.GetDirectories()
.Where(d => d.CreationTime > DateTime.Now.AddMonths(-1)) // 1个月内创建的
.OrderBy(d => d.Name); // 按名称排序
Console.WriteLine("n1个月内创建的文件夹:");
foreach (var dir in recentDirs)
{
Console.WriteLine($"{dir.Name} - 创建于: {dir.CreationTime}");
}
}
}
3. 高级过滤:自定义方法
using System;
using System.IO;
using System.Linq;
class AdvancedFilter
{
// 自定义过滤方法:检查文件是否是C#源代码且包含特定字符串
static bool IsCsFileWithKeyword(FileInfo file, string keyword)
{
if (file.Extension != ".cs")
return false;
try
{
string content = File.ReadAllText(file.FullName);
return content.Contains(keyword);
}
catch
{
return false;
}
}
static void Main()
{
string path = @"C:MyProject";
string searchKeyword = "async";
var resultFiles = new DirectoryInfo(path)
.EnumerateFiles("*.*", SearchOption.AllDirectories)
.Where(f => IsCsFileWithKeyword(f, searchKeyword));
Console.WriteLine($"包含 '{searchKeyword}' 的C#文件:");
foreach (var file in resultFiles)
{
Console.WriteLine(file.FullName);
}
}
}
六、获取文件和文件夹的详细属性
除了路径和名称外,有时还需要获取文件和文件夹的详细属性,如大小、创建时间、属性等。
using System;
using System.IO;
using System.Linq;
class FileProperties
{
static void Main()
{
string path = @"C:MyFolder";
if (!Directory.Exists(path))
{
Console.WriteLine("目录不存在");
return;
}
// 获取文件夹属性
DirectoryInfo dirInfo = new DirectoryInfo(path);
Console.WriteLine("文件夹属性:");
Console.WriteLine($"完整路径: {dirInfo.FullName}");
Console.WriteLine($"名称: {dirInfo.Name}");
Console.WriteLine($"父目录: {dirInfo.Parent?.FullName}");
Console.WriteLine($"创建时间: {dirInfo.CreationTime}");
Console.WriteLine($"最后访问时间: {dirInfo.LastAccessTime}");
Console.WriteLine($"属性: {dirInfo.Attributes}");
// 获取文件详细属性
FileInfo[] files = dirInfo.GetFiles().OrderByDescending(f => f.Length).Take(5).ToArray();
Console.WriteLine("n前5个最大的文件属性:");
foreach (FileInfo file in files)
{
Console.WriteLine($"n文件: {file.Name}");
Console.WriteLine($"完整路径: {file.FullName}");
Console.WriteLine($"大小: {file.Length} 字节");
Console.WriteLine($"创建时间: {file.CreationTime}");
Console.WriteLine($"最后修改时间: {file.LastWriteTime}");
Console.WriteLine($"最后访问时间: {file.LastAccessTime}");
Console.WriteLine($"属性: {file.Attributes}");
Console.WriteLine($"扩展名: {file.Extension}");
Console.WriteLine($"是否只读: {file.IsReadOnly}");
}
}
}
常用文件属性说明:
Attributes
:文件属性组合(ReadOnly、Hidden、System、Directory 等)Length
:文件大小(字节)CreationTime
:创建时间LastWriteTime
:最后修改时间LastAccessTime
:最后访问时间IsReadOnly
:是否只读
七、异步获取文件列表
在 UI 应用程序或需要非阻塞操作的场景中,异步获取文件列表可以避免界面卡顿。
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
class AsyncFileLister
{
// 异步获取所有文件
static async Task<List<FileInfo>> GetAllFilesAsync(string path)
{
List<FileInfo> files = new List<FileInfo>();
DirectoryInfo dirInfo = new DirectoryInfo(path);
if (!dirInfo.Exists)
return files;
// 异步获取当前目录文件
FileInfo[] currentFiles = dirInfo.GetFiles();
files.AddRange(currentFiles);
// 获取子目录并异步处理
DirectoryInfo[] subDirs = dirInfo.GetDirectories();
foreach (DirectoryInfo subDir in subDirs)
{
// 递归调用并等待结果
List<FileInfo> subDirFiles = await GetAllFilesAsync(subDir.FullName);
files.AddRange(subDirFiles);
}
return await Task.FromResult(files);
}
static async Task Main()
{
string path = @"C:MyFolder";
Console.WriteLine("开始异步获取文件列表...");
// 记录开始时间
DateTime startTime = DateTime.Now;
// 异步获取文件列表
List<FileInfo> allFiles = await GetAllFilesAsync(path);
// 计算耗时
TimeSpan elapsed = DateTime.Now - startTime;
Console.WriteLine($"异步获取完成,耗时 {elapsed.TotalSeconds} 秒");
Console.WriteLine($"共找到 {allFiles.Count} 个文件");
// 显示最大的5个文件
var largestFiles = allFiles.OrderByDescending(f => f.Length).Take(5);
Console.WriteLine("n最大的5个文件:");
foreach (var file in largestFiles)
{
Console.WriteLine($"{file.FullName} - {file.Length / 1024 / 1024} MB");
}
}
}
八、处理特殊目录
C# 可以访问各种特殊目录,如系统目录、用户目录、临时目录等,这些目录的获取方式略有不同。
using System;
using System.IO;
using System.Environment;
class SpecialDirectories
{
static void Main()
{
// 获取系统特殊目录
Dictionary<string, string> specialDirs = new Dictionary<string, string>
{
{ "系统目录", Environment.SystemDirectory },
{ "临时目录", Path.GetTempPath() },
{ "用户目录", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) },
{ "文档目录", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) },
{ "桌面目录", Environment.GetFolderPath(Environment.SpecialFolder.Desktop) },
{ "程序文件", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) },
{ "应用数据", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) }
};
// 显示特殊目录并列出其中的文件
foreach (var dir in specialDirs)
{
Console.WriteLine($"n{dir.Key}: {dir.Value}");
if (Directory.Exists(dir.Value))
{
try
{
string[] files = Directory.GetFiles(dir.Value, "*.*", SearchOption.TopDirectoryOnly);
Console.WriteLine($"包含 {files.Length} 个文件");
// 显示前5个文件
for (int i = 0; i < Math.Min(5, files.Length); i++)
{
Console.WriteLine($" {Path.GetFileName(files[i])}");
}
if (files.Length > 5)
{
Console.WriteLine(" ... 更多文件 ...");
}
}
catch (Exception ex)
{
Console.WriteLine($" 无法访问: {ex.Message}");
}
}
else
{
Console.WriteLine(" 目录不存在");
}
}
}
}
九、注意事项与最佳实践
1. 异常处理
文件系统操作可能会遇到各种异常,必须进行适当的处理:
// 完善的异常处理示例
try
{
string[] files = Directory.GetFiles("C:/UnknownFolder");
}
catch (DirectoryNotFoundException)
{
// 目录不存在
}
catch (UnauthorizedAccessException)
{
// 权限不足
}
catch (PathTooLongException)
{
// 路径过长(超过260字符)
}
catch (IOException)
{
// IO错误(如设备未就绪)
}
catch (Security.SecurityException)
{
// 安全权限不足
}
catch (ArgumentException)
{
// 路径参数无效
}
2. 性能优化
- 处理大量文件时,优先使用
EnumerateFiles()
而非GetFiles()
- 避免在循环中重复创建
DirectoryInfo
或FileInfo
实例 - 如需多次使用文件信息,使用
FileInfo
和DirectoryInfo
缓存信息 - 递归遍历大型目录时,考虑使用并行处理(Parallel.ForEach)
// 并行处理提高性能
string rootPath = @"C:LargeFolder";
var dirInfo = new DirectoryInfo(rootPath);
Parallel.ForEach(dirInfo.EnumerateDirectories(), subDir =>
{
try
{
var files = subDir.EnumerateFiles("*.log");
Console.WriteLine($"在 {subDir.Name} 中找到 {files.Count()} 个日志文件");
}
catch (Exception ex)
{
Console.WriteLine($"处理 {subDir.Name} 时出错: {ex.Message}");
}
});
3. 跨平台注意事项
在.NET Core/.NET 5 +
的跨平台环境中,需要注意:
- 路径分隔符:使用
Path.DirectorySeparatorChar
而非硬编码 ‘’ 或 ‘/’ - 路径长度:Linux/macOS 对路径长度限制较宽松
- 大小写敏感性:Linux/macOS 文件系统区分大小写
- 特殊目录:不同操作系统的特殊目录位置不同
// 跨平台路径处理
string crossPlatformPath = Path.Combine("data", "logs", "app.log");
Console.WriteLine($"跨平台路径: {crossPlatformPath}");
Console.WriteLine($"目录分隔符: {Path.DirectorySeparatorChar}");
十、总结
C# 提供了多种方式来读取文件夹和文件列表,选择合适的方法取决于具体需求:
方法 | 特点 | 适用场景 |
---|---|---|
Directory 静态方法 |
简单直接,无需创建实例 | 简单操作,一次性使用 |
DirectoryInfo 实例方法 |
面向对象,可缓存信息 | 多次操作同一目录,需要详细属性 |
枚举方法(Enumerate* ) |
延迟加载,内存占用低 | 大量文件,流式处理 |
自定义递归 | 高度可控,可添加自定义逻辑 | 复杂过滤,特殊遍历需求 |
异步方法 | 非阻塞,不冻结 UI | 桌面应用,需要响应性 |
无论使用哪种方法,都应注意异常处理、性能优化和跨平台兼容性。掌握这些技术可以帮助开发者高效地实现文件管理功能,处理各种复杂的文件系统场景。