1. 介紹
檔案操作幾乎總是會遇到程式外部狀況:檔案可能被刪除、移動、被其他程式鎖定、使用者突然沒有權限或磁碟沒空間。這些情況都可能導致例外 — 一種特殊情況,會中斷程式執行並“拋出”一個例外物件(Exception)。如果不捕捉並處理這類例外,程式會以錯誤結束,並向使用者顯示嚇人的紅色錯誤訊息(或者更糟——默默崩潰)。
在 C# 裡處理這類情況會用到 try-catch 區塊(俗稱「錯誤陷阱」)。它不只是用來「抓到」錯誤,還能讓你決定接下來怎麼做:顯示友善訊息、提示選擇其他檔案、把錯誤寫入日誌或是重試操作。
在實際應用中
如果你在寫一個命令列計算機,或許可以不處理檔案錯誤。但如果是企業文件處理程式或儲存進度的遊戲,不處理這些錯誤就像在走鋼索卻沒裝安全帶:遲早會出事。
2. 回顧例外處理
基本語法 try-catch
在進入具體的檔案場景前,回顧一下 try-catch 的基本語法(我們在例外相關講義中已經見過):
try
{
// 這裡是可能會「拋出」例外的程式碼
}
catch (Exception ex)
{
// 這裡我們捕捉所有可能的例外... 但最好別這麼做!
Console.WriteLine("發生了問題: " + ex.Message);
}
在 try 內放潛在危險的程式碼,在 catch 內處理如果出錯要怎麼做。
程式設計師的笑話:「try-catch 就像是錯誤的隱形帽。錯誤存在,但你看不到它!」
檔案相關的例外
當透過 .NET 做檔案操作時,常會遇到在創建流、讀寫時拋出的例外。以下是常見情況與對應的例外類型:
- 找不到檔案 → FileNotFoundException
- 沒有存取檔案/目錄的權限 → UnauthorizedAccessException
- 路徑問題(例如不允許的字元、路徑過長) → PathTooLongException, ArgumentException
- 檔案被其他程序使用 → IOException
- 磁碟空間不足 → IOException
大多數檔案操作相關的例外都是繼承自 IOException 的派生類。它們都繼承自基本型別 System.Exception。
3. 實作:捕捉並處理檔案操作錯誤
我們來看幾個典型案例,假設我們的「正在開發的應用」嘗試從檔案讀取一條問候語,處理它並顯示給使用者。這裡會用 try-catch 做錯誤檢查。
範例 1:處理「找不到檔案」
string filePath = "hello.txt";
try
{
string greeting = File.ReadAllText(filePath);
Console.WriteLine("檔案內容: " + greeting);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("找不到檔案!請確認檔案 " + filePath + " 是否存在。");
// 我們也可以輸出詳細資訊
Console.WriteLine("技術細節: " + ex.Message);
}
如果檔案 "hello.txt" 不存在,程式不會當掉,而是輸出友善提示。就這麼簡單讓程式對使用者的「笨拙操作」更有韌性。
範例 2:權限被拒
情況複雜一點:檔案存在,但我們沒權限讀取(例如有人故意只給寫入權限,或檔案放在受保護的目錄)。
try
{
string secret = File.ReadAllText("C:\\Windows\\System32\\config.txt");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("無法存取檔案!試著以管理員身分執行程式。");
Console.WriteLine("技術原因: " + ex.Message);
}
範例 3:一般的 IOException
有些錯誤可能是鎖定衝突(例如其他程序正在使用該檔案)、磁碟空間不足或硬體問題:
try
{
File.WriteAllText("important.txt", "重要資訊!");
}
catch (IOException ex)
{
Console.WriteLine("檔案操作發生錯誤:很可能是被其他程式使用或磁碟空間不足。");
Console.WriteLine("技術原因: " + ex.Message);
}
4. 捕捉多個例外:選擇性處理
有時候你要針對不同例外做不同處理。在 .NET 裡可以寫多個 catch 區塊——要從更具體的例外寫到更通用的(否則編譯器會抱怨):
try
{
string content = File.ReadAllText("file.txt");
Console.WriteLine(content);
}
catch (FileNotFoundException ex)
{
Console.WriteLine("找不到檔案。");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("無權限存取檔案!");
}
catch (IOException ex)
{
Console.WriteLine("其他輸入/輸出錯誤: " + ex.Message);
}
重要:如果把 catch (Exception ex) 放在最前面,後面的區塊就沒意義了,因為基類會先捕捉到一切!
5. 巢狀 try-catch 與重試機制
有時你不只想處理錯誤,還想給使用者機會「修正」——例如提示輸入存在的檔案路徑:
string filePath;
string content;
int attempts = 0;
const int maxAttempts = 3;
do
{
Console.Write("請輸入檔案路徑: ");
filePath = Console.ReadLine();
try
{
content = File.ReadAllText(filePath);
Console.WriteLine("檔案內容:\n" + content);
break;
}
catch (FileNotFoundException)
{
Console.WriteLine("找不到檔案!請再試一次。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("無法存取檔案!請試試其他檔案。");
}
attempts++;
}
while (attempts < maxAttempts);
if (attempts == maxAttempts)
Console.WriteLine("嘗試次數過多。");
這段程式碼就是個小小的「使用者友善介面」,幫助忘記檔案放哪裡的使用者。
6. 常見錯誤與注意事項:從懶惰到有意識
新手(甚至有經驗的開發者)常犯的一個錯誤是把所有例外都抓住,寫成 catch (Exception) 然後只輸出「發生錯誤!」而不追根究柢。這種做法有幾個問題:第一,它會掩蓋商業邏輯上的真實錯誤。第二,非檔案相關的錯誤(例如程式碼打錯或數學錯誤)會被吞掉,找到真正原因會變得很難。
更好的做法是只捕捉那些你能有意義處理的錯誤。如果不確定是什麼錯誤,寧可讓它「炸掉」——也就是不要捕捉,讓應用崩潰,這樣你可以看到呼叫堆疊來找 bug。
注意:有些例外可能會帶有內部原因(InnerException)。在寫錯誤日誌時分析它們能提供更詳細的診斷資訊。
另一個要點是:如果在 catch 後程式無法繼續(例如無法打開主要設定檔),可以用 return 結束執行,或者重新拋出例外(throw;),不要讓程式變成功能不全的「殭屍程式」。
GO TO FULL VERSION