前言
承接第一篇 (路徑操作、常用對話框),本篇深入 檔案、目錄、磁碟機實體操作:FileInfo、DirectoryInfo、DriveInfo 三大核心類別。掌握屬性存取、建立/複製/移動/刪除、搜尋、版本資訊、大小計算、屬性位元組合操作等實戰技巧。
核心類別繼承關係
FileSystemInfo (abstract)
├─ FileInfo -- 檔案實體
└─ DirectoryInfo -- 目錄實體
DriveInfo -- 獨立,不繼承 FileSystemInfo
File / Directory -- 靜態方法類別,單次操作效率較高
關鍵差異:FileInfo/DirectoryInfo 實例化時不檢查存在性,屬性值為首次查詢快取,後續需呼叫 Refresh() 更新;DriveInfo 屬性每次即時讀取;File/Directory 靜態方法每次皆執行安全性檢查。
一、FileInfo — 檔案實體操作
常用屬性
| 屬性 | 型別 | 說明 |
| Exists | bool | 檔案是否存在 (首次查詢快取) |
| Name / FullName | string | 檔名 / 完整路徑 |
| Extension | string | 副檔名 (含點號) |
| Directory / DirectoryName | DirectoryInfo / string | 所在目錄物件 / 目錄路徑 |
| Length | long | 檔案大小 (位元組) |
| IsReadOnly | bool | 唯讀屬性 |
| Attributes | FileAttributes | 檔案屬性位元旗標 (可組合) |
| CreationTime / LastAccessTime / LastWriteTime | DateTime | 建立/存取/修改時間 |
屬性位元操作 — 正確判斷與設定
var fi = new FileInfo(@"C:\Test\data.txt");
// 判斷唯讀 (錯誤寫法:fi.Attributes == FileAttributes.ReadOnly 會因其他屬性失敗)
bool isReadOnly = (fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
// 設定唯讀
fi.Attributes |= FileAttributes.ReadOnly; // 加入旗標
fi.Attributes &= ~FileAttributes.ReadOnly; // 移除旗標
fi.Attributes ^= FileAttributes.Hidden; // 切換隱藏
// 批次設定多個屬性
fi.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.Archive;
fi.Refresh(); // 重新讀取檔案系統更新屬性
檔案版本資訊 — FileVersionInfo
var fvi = FileVersionInfo.GetVersionInfo(@"C:\Windows\Notepad.exe");
Console.WriteLine($"檔案版本: {fvi.FileVersion}");
Console.WriteLine($"產品版本: {fvi.ProductVersion}");
Console.WriteLine($"公司名稱: {fvi.CompanyName}");
Console.WriteLine($"版權宣告: {fvi.LegalCopyright}");
Console.WriteLine($"內部名稱: {fvi.InternalName}");
Console.WriteLine($"原始檔名: {fvi.OriginalFilename}");
檔案內容判等 — 雜湊比對 (SHA256)
using System.Security.Cryptography;
public static bool FilesContentEqual(string path1, string path2)
{
using (var sha = SHA256.Create())
using (var fs1 = File.OpenRead(path1))
using (var fs2 = File.OpenRead(path2))
{
byte[] hash1 = sha.ComputeHash(fs1);
byte[] hash2 = sha.ComputeHash(fs2);
return StructuralComparisons.StructuralEqualityComparer.Equals(hash1, hash2);
}
}
檔案操作方法
| 方法 | 說明 | 備註 |
| CopyTo(dest, overwrite) | 複製檔案 | overwrite=true 覆蓋既有 |
| MoveTo(dest) | 移動/重命名 | 跨磁碟機自動複製+刪除 |
| Delete() | 刪除檔案 | 不存在不拋例外 |
| Replace(dest, backup, ignoreMetadata) | 以目前檔案取代目標,備份舊檔 | 原子性替換,適合熱更新 |
| Create() / CreateText() / Open(mode) | 建立/開啟 FileStream | 回傳 Stream 供讀寫 |
二、DirectoryInfo — 目錄實體操作
常用屬性
| 屬性 | 型別 | 說明 |
| Exists | bool | 目錄是否存在 |
| Name / FullName | string | 目錄名 / 完整路徑 |
| Parent / Root | DirectoryInfo | 父目錄 / 根目錄 |
| Attributes / CreationTime / LastAccessTime / LastWriteTime | 同 FileInfo | |
列舉檔案與子目錄
var di = new DirectoryInfo(@"C:\Source");
// 取得檔案 (支援萬用字元、遞迴)
FileInfo[] files = di.GetFiles("*.log", SearchOption.AllDirectories);
FileInfo[] filesTop = di.GetFiles("*.txt"); // 僅最上層
// 取得子目錄
DirectoryInfo[] dirs = di.GetDirectories("*", SearchOption.TopDirectoryOnly);
// 取得檔案+目錄混合
FileSystemInfo[] all = di.GetFileSystemInfos("*.cs", SearchOption.AllDirectories);
遞迴計算目錄大小
public static long GetDirectorySize(DirectoryInfo dir, bool includeSubDir = true)
{
long size = 0;
try
{
foreach (var f in dir.GetFiles()) size += f.Length;
if (includeSubDir)
foreach (var d in dir.GetDirectories()) size += GetDirectorySize(d, true);
}
catch (UnauthorizedAccessException) { /* 忽略無權限目錄 */ }
return size;
}
目錄操作方法
| 方法 | 說明 |
| Create() | 建立目錄 (含中間層級) |
| CreateSubdirectory(path) | 建立子目錄 |
| MoveTo(dest) | 移動/重命名目錄 (含內容) |
| Delete(recursive) | 刪除目錄,recursive=true 刪除內容 |
目錄複製 (FileInfo 無 CopyTo 目錄方法,需自行遞迴)
public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target)
{
if (target.FullName.Equals(source.FullName, StringComparison.OrdinalIgnoreCase)) return;
if (!target.Exists) target.Create();
foreach (var file in source.GetFiles())
file.CopyTo(Path.Combine(target.FullName, file.Name), true);
foreach (var dir in source.GetDirectories())
CopyDirectory(dir, target.CreateSubdirectory(dir.Name));
}
三、DriveInfo — 磁碟機資訊
// 取得所有邏輯磁碟機
DriveInfo[] drives = DriveInfo.GetDrives();
foreach (var d in drives)
{
Console.WriteLine($"磁碟機: {d.Name}"); // C:\
Console.WriteLine($" 類型: {d.DriveType}"); // Fixed, CDRom, Removable, Network, Ram, Unknown
Console.WriteLine($" 格式: {d.DriveFormat}"); // NTFS, FAT32, exFAT, CDFS
Console.WriteLine($" 就緒: {d.IsReady}"); // false 表示無媒體 (如空光碟機)
if (d.IsReady)
{
Console.WriteLine($" 可用空間: {d.AvailableFreeSpace:N0} bytes"); // 考慮磁碟配額
Console.WriteLine($" 總可用空間: {d.TotalFreeSpace:N0} bytes");
Console.WriteLine($" 總容量: {d.TotalSize:N0} bytes");
Console.WriteLine($" 根目錄: {d.RootDirectory.FullName}");
}
}
四、File / Directory 靜態方法 vs 實例方法選擇
| 情境 | 建議 | 理由 |
| 單次操作、不重用路徑 | File.Copy / Directory.GetFiles 等靜態方法 | 程式碼簡潔、內部快取路徑字串 |
| 同一路徑多次操作 (屬性、移動、刪除) | FileInfo / DirectoryInfo 實例 | 避免重複安全性檢查、可呼叫 Refresh() |
| 需要檔案/目錄屬性快取 | 實例方法 | 首次查詢快取,後續存取極快 |
| 高頻率、大量檔案批次處理 | 靜態方法 + 並行 | 避免建立大量物件開銷 |
五、常見例外處理模式
try
{
fi.CopyTo(dest, true);
}
catch (FileNotFoundException) { /* 來源不存在 */ }
catch (DirectoryNotFoundException) { /* 目錄不存在 */ }
catch (UnauthorizedAccessException) { /* 權限不足 */ }
catch (IOException ex) when (ex.HResult == -2147024864) { /* 檔案被鎖定/共用違規 */ }
catch (PathTooLongException) { /* 路徑超過 260 字元 */ }
catch (NotSupportedException) { /* 路徑格式無效 */ }
六、路徑過長 ( > 260 字元) 解決方案
- .NET 4.6.2+ / .NET Core:專案
.csproj 加入 `true`,並使用 `\\?\` 前綴:var fi = new FileInfo(@"\\?\C:\Very\Long\Path\file.txt");
- .NET Framework 舊版:P/Invoke
CopyFileW、DeleteFileW 等 Unicode API。
- 縮短路徑:使用 `subst` 映射虛擬磁碟機、啟用 NTFS 長路徑支援 (群組原則)。