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

任意のメソッド呼び出しでスローできる例外 (システムに負荷がかかっているときの OutOfMemoryException やプログラム エラーによる NullReferenceException など) に加え、.NET ファイル システムのメソッドは、次の例外をスローできます。In addition to the exceptions that can be thrown in any method call (such as an OutOfMemoryException when a system is stressed or an NullReferenceException due to programmer error), .NET file system methods can throw the following exceptions:

エラー コードの例外へのマッピングMapping error codes to exceptions

ファイル システムは、オペレーティング システムのリソースであるため、.NET Core と .NET Framework の両方の I/O メソッドは、基になるオペレーティング システムに呼び出しをラップします。Because the file system is an operating system resource, I/O methods in both .NET Core and .NET Framework wrap calls to the underlying operating system. オペレーティング システムによって実行されるコードで I/O エラーが発生すると、オペレーティング システムは、.NET の I/O メソッドにエラー情報を返します。When an I/O error occurs in code executed by the operating system, the operating system returns error information to the .NET I/O method. その後、メソッドがエラー情報を .NET 例外の種類に変換します。通常はエラー コードの形式に変換されます。The method then translates the error information, typically in the form of an error code, into a .NET exception type. ほとんどの場合、これは、エラー コードを対応する例外の種類に直接変換することによって実行され、メソッド呼び出しのコンテキストに基づく特別なエラーのマッピングは行われません。In most cases, it does this by directly translating the error code into its corresponding exception type; it does not perform any special mapping of the error based on the context of the method call.

たとえば、Windows オペレーティング システムでは、エラー コードERROR_FILE_NOT_FOUND (または 0x02) を返すメソッドの呼び出しは FileNotFoundException にマップされ、エラー コード ERROR_PATH_NOT_FOUND (または 0x03) は DirectoryNotFoundException にマップされます。For example, on the Windows operating system, a method call that returns an error code of ERROR_FILE_NOT_FOUND (or 0x02) maps to a FileNotFoundException, and an error code of ERROR_PATH_NOT_FOUND (or 0x03) maps to a DirectoryNotFoundException.

ただし、オペレーティング システムが特定のエラー コードが返す正確な条件は、多くの場合、文書化されていないか、適切に文書化されていません。However, the precise conditions under which the operating system returns particular error codes is often undocumented or poorly documented. その結果、予期しない例外が発生する可能性があります。As a result, unexpected exceptions can occur. たとえば、ファイルではなくディレクトリを操作している場合、無効なディレクトリ パスを DirectoryInfo.DirectoryInfo に提供すると、DirectoryNotFoundException コンストラクターがスローされることが想定されます。For example, because you are working with a directory rather than a file, you would expect that providing an invalid directory path to the DirectoryInfo.DirectoryInfo constructor throws a DirectoryNotFoundException. ただし、FileNotFoundException がスローされる場合もあります。However, it may also throw a FileNotFoundException.

I/O 操作の例外処理Exception handling in I/O operations

オペレーティング システムへのこの依存性によって、同じ例外条件 (例に示したディレクトリが見つからないというエラーなど) で、I/O メソッドが I/O 例外クラス全体のいずれかをスルーする可能性があります。Because of this reliance on the operating system, identical exception conditions (such as the directory not found error in our example) can result in an I/O method throwing any one of the entire class of I/O exceptions. これは、I/O API を呼び出すときは、次の表に示すように、これらの例外のほとんどまたはすべてを処理するようにコードを準備する必要があることを意味します。This means that, when calling I/O APIs, your code should be prepared to handle most or all of these exceptions, as shown in the following table:

例外の種類Exception type .NET Core.NET Core .NET Framework.NET Framework
IOException [はい]Yes はいYes
FileNotFoundException はいYes はいYes
DirectoryNotFoundException はいYes はいYes
DriveNotFoundException はいYes はいYes
PathTooLongException はいYes はいYes
OperationCanceledException はいYes はいYes
UnauthorizedAccessException はいYes [はい]Yes
ArgumentException .NET Core 2.0 以前.NET Core 2.0 and earlier はいYes
NotSupportedException ×No はいYes
SecurityException ×No 限定的な信頼のみLimited trust only

IOException の処理Handling IOException

System.IO 名前空間内の例外の基底クラスとして、定義済みの例外の種類にマップされないすべてのエラー コードに対して IOException もスローされます。As the base class for exceptions in the System.IO namespace, IOException is also thrown for any error code that does not map to a predefined exception type. つまり、すべての I/O 操作によってスローされる可能性があります。This means that it can be thrown by any I/O operation.

重要

IOException は、System.IO 名前空間内の他の例外の種類の基底クラスであるため、他の I/O 関連例外を処理した後で catch ブロック内で処理する必要があります。Because IOException is the base class of the other exception types in the System.IO namespace, you should handle in a catch block after you've handled the other I/O-related exceptions.

さらに、.NET Core 2.1 以降、パスの正確性の検証チェック (たとえばパス内に無効な文字が存在しないことを確認するためのチェック) が削除されており、ランタイムでは、独自の検証コードではなく、オペレーティング システムのエラー コードからマップされた例外がスローされます。In addition, starting with .NET Core 2.1, validation checks for path correctness (for example, to ensure that invalid characters are not present in a path) have been removed, and the runtime throws an exception mapped from an operating system error code rather than from its own validation code. この場合、最もスローされる可能性が高い例外は IOException ですが、その他の種類の例外もスローされる可能性があります。The most likely exception to be thrown in this case is an IOException, although any other exception type could also be thrown.

例外処理コードでは、IOException は常に最後に処理する必要があることに注意してください。Note that, in your exception handling code, you should always handle the IOException last. それ以外の場合、それは他のすべての IO 例外の基底クラスであるため、派生クラスの catch ブロックは評価されません。Otherwise, because it is the base class of all other IO exceptions, the catch blocks of derived classes will not be evaluated.

IOException の場合、IOException.HResult プロパティから追加のエラー情報を取得できます。In the case of an IOException, you can get additional error information from the IOException.HResult property. HResult 値を Win32 エラー コードに変換するには、32 ビット値の上位 16 ビットを削除します。To convert the HResult value to a Win32 error code, you strip out the upper 16 bits of the 32-bit value. 次の表に、IOException にラップされる可能性があるエラー コードの一覧を示します。The following table lists error codes that may be wrapped in an IOException.

HResultHResult 定数Constant 説明Description
ERROR_SHARING_VIOLATIONERROR_SHARING_VIOLATION 3232 ファイル名が存在しないか、ファイルまたはディレクトリが使用中です。The file name is missing, or the file or directory is in use.
ERROR_FILE_EXISTSERROR_FILE_EXISTS 8080 ファイルは既に存在します。The file already exists.
ERROR_INVALID_PARAMETERERROR_INVALID_PARAMETER 8787 メソッドに指定された引数が無効です。An argument supplied to the method is invalid.
ERROR_ALREADY_EXISTSERROR_ALREADY_EXISTS 183183 ファイルまたはディレクトリは既に存在します。The file or directory already exists.

catch ステートメントの When 句を使用してこれらを処理できます。次に例を示します。You can handle these using a When clause in a catch statement, as the following example shows.

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 maxium 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 maxium 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

関連項目See also