1. 介绍
在 .NET 中用于处理文件和文件夹的类分为两类,理念不同:
静态类: File, Directory — 像一组工具函数:调用方法,传入路径,得到结果。
实例类: FileInfo, DirectoryInfo — 把具体的文件和文件夹表示成带属性和方法的对象。
为什么两种方式都存在?这是在使用简便性(静态方法)和面向对象灵活性(实例类)之间的折中。每种在不同场景下更方便。
2. 静态类的理念
类 File 和 Directory 遵循“直接做事”的原则。它们提供了直接的接口来对文件系统执行操作,不需要创建中间对象。
// 检查文件是否存在
if (File.Exists("document.txt"))
{
// 读取全部文本
string content = File.ReadAllText("document.txt");
// 创建备份
File.Copy("document.txt", "document_backup.txt");
// 删除原文件
File.Delete("document.txt");
}
这对简单、一时性的操作很方便:不需要考虑对象、生命周期或状态——只要用路径调用方法即可。
Directory 的用法类似:
// 创建文件夹
Directory.CreateDirectory(@"C:\MyProject\Data");
// 获取所有文本文件
string[] textFiles = Directory.GetFiles(@"C:\MyProject", "*.txt");
// 检查文件夹是否存在
if (Directory.Exists(@"C:\Temp"))
{
Directory.Delete(@"C:\Temp", recursive: true);
}
3. 实践示例比较方法
示例 1:简单复制文件
静态方法:
string sourcePath = "original.txt";
string destinationPath = "copy.txt";
if (File.Exists(sourcePath))
{
File.Copy(sourcePath, destinationPath, overwrite: true);
Console.WriteLine("文件已复制");
}
else
{
Console.WriteLine("未找到源文件");
}
实例方式:
var sourceFile = new FileInfo("original.txt");
var destinationFile = new FileInfo("copy.txt");
if (sourceFile.Exists)
{
sourceFile.CopyTo(destinationFile.FullName, overwrite: true);
Console.WriteLine("文件已复制");
}
else
{
Console.WriteLine("未找到源文件");
}
这里静态方式更简洁。但如果需要额外信息呢?
示例 2:带大小检查的复制
静态方法:
string sourcePath = "largefile.zip";
string destinationPath = "backup.zip";
if (File.Exists(sourcePath))
{
var fileInfo = new FileInfo(sourcePath); // 还是得创建对象!
if (fileInfo.Length > 100 * 1024 * 1024) // 大于 100 MB
{
Console.WriteLine($"注意:正在复制大文件 ({fileInfo.Length / 1024 / 1024} MB)");
}
File.Copy(sourcePath, destinationPath, overwrite: true);
}
实例方式:
var sourceFile = new FileInfo("largefile.zip");
if (sourceFile.Exists)
{
if (sourceFile.Length > 100 * 1024 * 1024) // 大于 100 MB
{
Console.WriteLine($"注意:正在复制大文件 ({sourceFile.Length / 1024 / 1024} MB)");
}
sourceFile.CopyTo("backup.zip", overwrite: true);
}
在这里面向对象的方式更自然:把文件当成对象,直接使用它的属性。
示例 3:分析文件夹内容
静态方法:
string folderPath = @"C:\Documents";
if (Directory.Exists(folderPath))
{
string[] files = Directory.GetFiles(folderPath);
string[] subdirs = Directory.GetDirectories(folderPath);
Console.WriteLine($"文件数: {files.Length}, 文件夹数: {subdirs.Length}");
// 要获取大小,还是需要 FileInfo 对象
long totalSize = 0;
foreach (string filePath in files)
{
var fileInfo = new FileInfo(filePath);
totalSize += fileInfo.Length;
}
Console.WriteLine($"总大小: {totalSize / 1024} KB");
}
实例方式:
var folder = new DirectoryInfo(@"C:\Documents");
if (folder.Exists)
{
var files = folder.GetFiles();
var subdirs = folder.GetDirectories();
Console.WriteLine($"文件数: {files.Length}, 文件夹数: {subdirs.Length}");
long totalSize = files.Sum(f => f.Length); // 信息已经可用!
Console.WriteLine($"总大小: {totalSize / 1024} KB");
}
这里 DirectoryInfo 更有优势:返回的 FileInfo[] 集合已包含元数据。
4. 选择方法的标准
当满足这些情况时使用静态类(File/Directory):
- 操作简单且一次性的。 快速检查文件是否存在、删除或读取内容——静态方法最合适。
// 简单读取配置
if (File.Exists("config.json"))
{
string config = File.ReadAllText("config.json");
// 处理配置...
}
- 不需要文件属性信息。 如果不关心大小、时间或属性,静态调用更简洁。
- 你的逻辑以路径字符串为主,而不是把文件当对象。 当逻辑操作字符串路径时,静态方法更直观。
当满足这些情况时使用实例类(FileInfo/DirectoryInfo):
- 需要关于单个文件/文件夹的许多信息。 像 Length、CreationTime、Attributes 这些属性可以直接获取。
var logFile = new FileInfo("application.log");
Console.WriteLine($"日志大小: {logFile.Length / 1024} KB");
Console.WriteLine($"最后修改: {logFile.LastWriteTime}");
Console.WriteLine($"位置: {logFile.Directory.FullName}");
- 对同一个文件执行多次操作。 创建对象一次,用于多种操作更高效。
var document = new FileInfo("report.docx");
if (document.Exists)
{
var backup = document.CopyTo($"report_backup_{DateTime.Now:yyyyMMdd}.docx");
document.MoveTo("archive/report.docx");
Console.WriteLine($"文档已归档,已创建副本 {backup.Name}");
}
- 处理文件集合。 GetFiles() 和 GetDirectories() 返回带完整信息的对象。
- 需要面向对象的架构。 文件/文件夹作为对象更容易传递、存储并在 LINQ 中使用。
5. 性能和缓存的特点
实例类中的缓存
FileInfo/DirectoryInfo 的一个关键特性是元数据缓存。第一次访问某个属性(例如 Length 或 CreationTime)时,.NET 会进行系统调用,加载所有信息并在对象内部缓存它们。
var file = new FileInfo("document.txt");
// 第一次访问 - 系统调用加载所有元数据
long size = file.Length;
// 后续访问使用缓存 - 非常快
DateTime created = file.CreationTime;
DateTime modified = file.LastWriteTime;
bool readOnly = file.IsReadOnly;
如果需要查询同一个文件的多个属性,面向对象的方式更高效:一次系统调用而不是多次。
缓存过期的问题
反过来说,信息可能会过期(如果文件在程序外被修改)。使用 Refresh():
var file = new FileInfo("data.txt");
Console.WriteLine($"大小: {file.Length}"); // 比如 1000 字节
// 这期间另一个程序修改了文件...
Console.WriteLine($"大小: {file.Length}"); // 仍然是缓存里的 1000 字节!
// 强制更新
file.Refresh();
Console.WriteLine($"大小: {file.Length}"); // 现在是最新的大小
静态调用总是直接访问文件系统,所以不会有这个问题。
大规模操作
在大量文件上选择 API 会影响性能:
// 静态方式 — 会有很多系统调用
string[] files = Directory.GetFiles(@"C:\Photos");
foreach (string filePath in files)
{
var info = new FileInfo(filePath); // 对每个文件都做系统调用
if (info.Length > 10 * 1024 * 1024) // 大于 10 MB
{
Console.WriteLine($"大图像: {info.Name}");
}
}
// 实例方式 — 每个文件夹只有一次系统调用
var photosDir = new DirectoryInfo(@"C:\Photos");
foreach (var file in photosDir.GetFiles()) // 信息一次性加载
{
if (file.Length > 10 * 1024 * 1024)
{
Console.WriteLine($"大图像: {file.Name}");
}
}
6. API 与功能差异
实例类的独特能力
var file = new FileInfo(@"C:\Projects\MyApp\source\Program.cs");
// 在目录层级中导航
DirectoryInfo projectDir = file.Directory.Parent; // MyApp
DirectoryInfo sourceDir = file.Directory; // source
// 文件的详细信息
Console.WriteLine($"扩展名: {file.Extension}");
Console.WriteLine($"只读: {file.IsReadOnly}");
Console.WriteLine($"属性: {file.Attributes}");
// 把目录当对象来操作
var dir = new DirectoryInfo(@"C:\Projects");
DirectoryInfo parent = dir.Parent; // C:\
DirectoryInfo root = dir.Root; // C:\
静态类的独特能力
// 一行读写文本
string content = File.ReadAllText("config.txt");
File.WriteAllText("output.txt", "Hello World");
// 字符串操作
string[] lines = File.ReadAllLines("data.txt");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });
// 追加文本到文件
File.AppendAllText("log.txt", $"{DateTime.Now}: Application started\n");
// 处理字节
byte[] data = File.ReadAllBytes("image.jpg");
File.WriteAllBytes("copy.jpg", data);
7. 实用建议
场景 1:备份工具
如果需要根据大小和时间戳来复制,实例类更方便:
public void BackupDirectory(string sourcePath, string backupPath)
{
var sourceDir = new DirectoryInfo(sourcePath);
var backupDir = new DirectoryInfo(backupPath);
if (!backupDir.Exists)
backupDir.Create();
foreach (var file in sourceDir.GetFiles())
{
var backupFile = new FileInfo(Path.Combine(backupPath, file.Name));
// 仅当文件更新或不存在时才复制
if (!backupFile.Exists || file.LastWriteTime > backupFile.LastWriteTime)
{
file.CopyTo(backupFile.FullName, overwrite: true);
Console.WriteLine($"已复制: {file.Name} ({file.Length / 1024} KB)");
}
}
}
场景 2:简单文本文件操作
存取简单数据时,静态方法足够:
public void SaveUserPreferences(string username, string theme, bool notifications)
{
string configPath = "user.config";
string[] settings = {
$"Username={username}",
$"Theme={theme}",
$"Notifications={notifications}"
};
File.WriteAllLines(configPath, settings);
}
public Dictionary<string, string> LoadUserPreferences()
{
string configPath = "user.config";
var preferences = new Dictionary<string, string>();
if (!File.Exists(configPath))
{
// 创建默认设置
SaveUserPreferences("User", "Light", true);
return LoadUserPreferences();
}
string[] lines = File.ReadAllLines(configPath);
foreach (string line in lines)
{
if (line.Contains('='))
{
string[] parts = line.Split('=', 2);
preferences[parts[0]] = parts[1];
}
}
return preferences;
}
场景 3:文件系统分析
生成磁盘空间报告时,实例类最合适:
public void AnalyzeDiskUsage(string path)
{
var directory = new DirectoryInfo(path);
var report = new Dictionary<string, long>();
foreach (var file in directory.GetFiles("*", SearchOption.AllDirectories))
{
string extension = file.Extension.ToLower();
if (string.IsNullOrEmpty(extension))
extension = "(无扩展名)";
if (!report.ContainsKey(extension))
report[extension] = 0;
report[extension] += file.Length;
}
var sortedReport = report.OrderByDescending(kvp => kvp.Value);
foreach (var item in sortedReport.Take(10))
{
Console.WriteLine($"{item.Key}: {item.Value / 1024 / 1024} MB");
}
}
8. 常见错误和陷阱
错误:不必要地混用两种方法
有时先用静态方法然后又为了元数据创建对象——会导致不必要的重复访问:
// 低效 - 对文件系统调用了两次
if (File.Exists("document.txt"))
{
var fileInfo = new FileInfo("document.txt"); // 重复了存在性检查
Console.WriteLine($"大小: {fileInfo.Length}");
}
// 更好是直接使用对象方式
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"大小: {fileInfo.Length}");
}
错误:忽略过期缓存
当长期监控可能被外部修改的文件时,要用 Refresh() 更新缓存:
var logFile = new FileInfo("application.log");
while (true)
{
logFile.Refresh(); // 更新文件信息
if (logFile.Length > 100 * 1024 * 1024) // 100 MB
{
// 归档日志
logFile.MoveTo($"logs/archived_{DateTime.Now:yyyyMMdd_HHmmss}.log");
break;
}
Thread.Sleep(60000); // 每分钟检查一次
}
错误:对大规模操作选择不当
处理成千上万文件时 API 的选择对性能至关重要:
// 慢 - 很多小的文件系统调用
string[] allFiles = Directory.GetFiles(@"C:\BigFolder", "*", SearchOption.AllDirectories);
var largeFiles = allFiles.Where(path => new FileInfo(path).Length > 1024 * 1024).ToList();
// 更快 - 使用已经准备好的信息
var folder = new DirectoryInfo(@"C:\BigFolder");
var largeFiles = folder.GetFiles("*", SearchOption.AllDirectories)
.Where(file => file.Length > 1024 * 1024)
.ToList();
GO TO FULL VERSION