Share via


.NET での I/O エラーの処理

任意のメソッド呼び出しでスローできる例外 (システムに負荷がかかっているときの OutOfMemoryException やプログラム エラーによる NullReferenceException など) に加え、.NET ファイル システムのメソッドは、次の例外をスローできます。

エラー コードの例外へのマッピング

ファイル システムは、オペレーティング システムのリソースであるため、.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

関連項目