.NET での I/O エラーの処理
任意のメソッド呼び出しでスローできる例外 (システムに負荷がかかっているときの 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 もスローされます。 つまり、すべての I/O 操作によってスローされる可能性があります。
重要
IOException は、System.IO 名前空間内の他の例外の種類の基底クラスであるため、他の I/O 関連例外を処理した後で catch
ブロック内で処理する必要があります。
さらに、.NET Core 2.1 以降、パスの正確性の検証チェック (たとえばパス内に無効な文字が存在しないことを確認するためのチェック) が削除されており、ランタイムでは、独自の検証コードではなく、オペレーティング システムのエラー コードからマップされた例外がスローされます。 この場合、最もスローされる可能性が高い例外は IOException ですが、その他の種類の例外もスローされる可能性があります。
例外処理コードでは、IOException は常に最後に処理する必要があることに注意してください。 それ以外の場合、それは他のすべての IO 例外の基底クラスであるため、派生クラスの catch ブロックは評価されません。
IOException の場合、IOException.HResult プロパティから追加のエラー情報を取得できます。 HResult 値を Win32 エラー コードに変換するには、32 ビット値の上位 16 ビットを削除します。 次の表に、IOException にラップされる可能性があるエラー コードの一覧を示します。
HResult | 定数 | 説明 |
---|---|---|
ERROR_SHARING_VIOLATION | 32 | ファイル名が存在しないか、ファイルまたはディレクトリが使用中です。 |
ERROR_FILE_EXISTS | 80 | ファイルは既に存在します。 |
ERROR_INVALID_PARAMETER | 87 | メソッドに指定された引数が無効です。 |
ERROR_ALREADY_EXISTS | 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
関連項目
.NET