CodeGym /课程 /C# SELF /比较方法 File vs

比较方法 File vs FileInfo

C# SELF
第 39 级 , 课程 3
可用

1. 介绍

在 .NET 中用于处理文件和文件夹的类分为两类,理念不同:

静态类: File, Directory — 像一组工具函数:调用方法,传入路径,得到结果。

实例类: FileInfo, DirectoryInfo — 把具体的文件和文件夹表示成带属性和方法的对象。

为什么两种方式都存在?这是在使用简便性(静态方法)和面向对象灵活性(实例类)之间的折中。每种在不同场景下更方便。

2. 静态类的理念

FileDirectory 遵循“直接做事”的原则。它们提供了直接的接口来对文件系统执行操作,不需要创建中间对象。

// 检查文件是否存在
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):

  • 需要关于单个文件/文件夹的许多信息。LengthCreationTimeAttributes 这些属性可以直接获取。
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 的一个关键特性是元数据缓存。第一次访问某个属性(例如 LengthCreationTime)时,.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();
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION