除了可以在任何方法呼叫中擲回的例外狀況(例如當系統承受壓力時會擲回的 OutOfMemoryException 或因程式設計人員錯誤而擲回的 NullReferenceException)之外,.NET 文件系統方法還可以擲回下列例外狀況:
- System.IO.IOException,所有 System.IO 例外狀況類型的基類。 當作業系統的返回碼無法直接對應至任何其他例外狀況類型時,會擲回這些錯誤。
- System.IO.FileNotFoundException。
- System.IO.DirectoryNotFoundException。
- DriveNotFoundException。
- System.IO.PathTooLongException。
- System.OperationCanceledException。
- System.UnauthorizedAccessException。
- System.ArgumentException,會在 .NET Framework 及 .NET Core 2.0 及更早的版本中,因無效的路徑字元而拋出。
- System.NotSupportedException 會在 .NET Framework 中因無效的冒號而拋出。
- System.Security.SecurityException,這是針對在有限信任中執行的應用程式擲回,而此信任僅缺少 .NET Framework 的必要許可權。 (完全信任是 .NET Framework 上的預設值。
將錯誤碼對應至例外狀況
因為檔案系統是作業系統資源,.NET Core 和 .NET Framework 中的 I/O 方法都會包裝對基礎作業系統的呼叫。 當操作系統執行的程式代碼中發生 I/O 錯誤時,操作系統會將相關的錯誤資訊傳回至 .NET I/O 方法。 然後,方法會將錯誤資訊,通常是以錯誤碼的形式轉譯為 .NET 例外狀況類型。 在大部分情況下,它會藉由將錯誤碼直接轉譯成其對應的例外狀況類型來執行這項作業;它不會根據方法呼叫的內容執行錯誤的任何特殊對應。
例如,在 Windows 作業系統上,傳回錯誤碼為 ERROR_FILE_NOT_FOUND
(或 0x02)的 方法呼叫會對應到 FileNotFoundException,而傳回錯誤碼為 ERROR_PATH_NOT_FOUND
(或 0x03)的 方法呼叫會對應到 DirectoryNotFoundException。
不過,作系統傳回特定錯誤碼的精確條件通常未記載或記錄不佳。 因此,可能會發生非預期的例外狀況。 例如,由於您正在使用目錄而非檔案,因此當您為建構函式提供一個無效的目錄路徑 DirectoryInfo 時,預期會擲回一個 DirectoryNotFoundException 錯誤。 不過,它也可能拋出 FileNotFoundException。
I/O 作業中的例外狀況處理
由於對作業系統的依賴,相同的例外狀況條件(例如範例中找不到目錄的錯誤)可能會導致 I/O 方法擲回任何一種 I/O 例外狀況類別。 這表示,呼叫 I/O API 時,您的程式代碼應該準備好處理大部分或全部的例外狀況,如下表所示:
例外狀況類型 | .NET Core/.NET 5+ | .NET Framework |
---|---|---|
IOException | 是的 | 是的 |
FileNotFoundException | 是的 | 是的 |
DirectoryNotFoundException | 是的 | 是的 |
DriveNotFoundException | 是的 | 是的 |
PathTooLongException | 是的 | 是的 |
OperationCanceledException | 是的 | 是的 |
UnauthorizedAccessException | 是的 | 是的 |
ArgumentException | .NET Core 2.0 和更早版本 | 是的 |
NotSupportedException | 否 | 是的 |
SecurityException | 否 | 僅限有限信任 |
處理 IOException
作為命名空間中例外狀況的 System.IO 基類, IOException 也會針對未對應至預先定義的例外狀況類型的任何錯誤碼擲回 。 這表示它可以由任何輸入/輸出作業拋出。
這很重要
因為 IOException 是命名空間中 System.IO 其他例外狀況類型的基類,因此在處理其他 I/O 相關例外狀況之後,您應該在 區塊中 catch
處理 。
此外,從 .NET Core 2.1 開始,已移除驗證檢查路徑正確性的功能(例如,確保路徑中沒有無效字元),並且執行階段會拋出來自作業系統錯誤碼對應的例外狀況,而不是來自行錯誤驗證碼的例外狀況。 在此情況下,最可能擲回的例外狀況是 IOException,雖然也可以擲回任何其他例外狀況類型。
請注意,在您的例外狀況處理程式碼中,應該總是將 IOException 的處理放在最後。 否則,因為它是所有其他 IO 例外狀況的基類,因此不會評估衍生類別的 catch 區塊。
如果是 IOException,您可以從 IOException.HResult 屬性取得其他錯誤資訊。 若要將 HResult 值轉換成 Win32 錯誤碼,您可以去除 32 位值的上層 16 位。 下表列出可能包含在 IOException 中的錯誤碼。
HResult | 常數 | 說明 |
---|---|---|
共享違規錯誤 | 32 | 檔名遺失,或檔案或目錄正在使用中。 |
ERROR_FILE_EXISTS(檔案已存在) | 80 | 檔案已經存在。 |
ERROR_INVALID_PARAMETER(錯誤:無效的參數) | 87 | 提供給方法的自變數無效。 |
錯誤_已經存在 | 183 | 檔案或目錄已經存在。 |
您可以使用 catch 語句中的 When
子句來處理這些專案,如下列範例所示。
using System;
using System.IO;
using System.Text;
class Program
{
static void Main()
{
var sw = OpenStream(@".\textfile.txt");
if (sw is null)
return;
sw.WriteLine("This is the first line.");
sw.WriteLine("This is the second line.");
sw.Close();
}
static StreamWriter? OpenStream(string path)
{
if (path is null)
{
Console.WriteLine("You did not supply a file path.");
return null;
}
try
{
var fs = new FileStream(path, FileMode.CreateNew);
return new StreamWriter(fs);
}
catch (FileNotFoundException)
{
Console.WriteLine("The file or directory cannot be found.");
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("The file or directory cannot be found.");
}
catch (DriveNotFoundException)
{
Console.WriteLine("The drive specified in 'path' is invalid.");
}
catch (PathTooLongException)
{
Console.WriteLine("'path' exceeds the maximum supported path length.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("You do not have permission to create this file.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32)
{
Console.WriteLine("There is a sharing violation.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 80)
{
Console.WriteLine("The file already exists.");
}
catch (IOException e)
{
Console.WriteLine($"An exception occurred:\nError code: " +
$"{e.HResult & 0x0000FFFF}\nMessage: {e.Message}");
}
return null;
}
}
Imports System.IO
Module Program
Sub Main(args As String())
Dim sw = OpenStream(".\textfile.txt")
If sw Is Nothing Then Return
sw.WriteLine("This is the first line.")
sw.WriteLine("This is the second line.")
sw.Close()
End Sub
Function OpenStream(path As String) As StreamWriter
If path Is Nothing Then
Console.WriteLine("You did not supply a file path.")
Return Nothing
End If
Try
Dim fs As New FileStream(path, FileMode.CreateNew)
Return New StreamWriter(fs)
Catch e As FileNotFoundException
Console.WriteLine("The file or directory cannot be found.")
Catch e As DirectoryNotFoundException
Console.WriteLine("The file or directory cannot be found.")
Catch e As DriveNotFoundException
Console.WriteLine("The drive specified in 'path' is invalid.")
Catch e As PathTooLongException
Console.WriteLine("'path' exceeds the maximum supported path length.")
Catch e As UnauthorizedAccessException
Console.WriteLine("You do not have permission to create this file.")
Catch e As IOException When (e.HResult And &h0000FFFF) = 32
Console.WriteLine("There is a sharing violation.")
Catch e As IOException When (e.HResult And &h0000FFFF) = 80
Console.WriteLine("The file already exists.")
Catch e As IOException
Console.WriteLine($"An exception occurred:{vbCrLf}Error code: " +
$"{e.HResult And &h0000FFFF}{vbCrLf}Message: {e.Message}")
End Try
Return Nothing
End Function
End Module