CodeGym /課程 /C# SELF /比較兩種做法 File vs

比較兩種做法 File vs FileInfo

C# SELF
等級 39, 課堂 3
開放

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[] 集合已包含 metadata。

4. 選擇準則

當使用靜態類別 (File/Directory):

  • 操作簡單且一次性。 想快速檢查檔案存在、刪除或讀取內容 — 靜態方法很適合。
// 簡單讀配置
if (File.Exists("config.json"))
{
    string config = File.ReadAllText("config.json");
    // 處理配置...
}
  • 不需要檔案屬性資訊。 若不在意大小、時間或 attributes,靜態呼叫更短更直觀。
  • 你是在處理路徑字串,而不是檔案物件。 當邏輯以路徑字串為主,靜態方法更自然。

當使用實例類別 (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. 效能與快取特性

實例類別的快取

/ 的關鍵特性之一是 metadata 的快取。第一次存取某個屬性(例如 Length 或 CreationTime)時,.NET 會做系統呼叫,把所有資訊載入並快取在物件內。

var file = new FileInfo("document.txt");

// 第一次存取 - 系統呼叫並載入所有 metadata
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 bytes

// 這時候別的程式修改了檔案...

Console.WriteLine($"大小: {file.Length}"); // 仍然是快取的 1000 bytes!

// 強制更新
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. 常見錯誤與陷阱

錯誤:不必要地混用兩種做法

有時會先用靜態方法檢查,然後又建立物件去取 metadata — 造成多餘且重複的系統呼叫:

// 低效率 - 兩次對檔案系統的呼叫
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

處理成千上萬個檔案時,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();
2
任務
C# SELF, 等級 39, 課堂 3
上鎖
分析資料夾內容
分析資料夾內容
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION