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. 效能與快取特性
實例類別的快取
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();
GO TO FULL VERSION