.NET에서 I/O 오류 처리Handling I/O errors in .NET

메서드 호출에서 throw될 수 있는 예외(예: 시스템 부하가 큰 경우의 OutOfMemoryException 또는 프로그래머 오류로 인한 NullReferenceException) 외에도, .NET 파일 시스템 메서드는 다음과 같은 예외를 throw할 수 있습니다.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이 throw됩니다.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이 throw될 수도 있습니다.However, it may also throw a FileNotFoundException.

I/O 작업의 예외 처리Exception handling in I/O operations

이러한 운영 체제 사용 때문에 예제의 디렉터리를 찾을 수 없음 오류와 같은 동일한 예외 조건에서 I/O 메서드가 I/O 예외의 전체 클래스 중 하나를 임의로 throw할 수 있습니다.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 yesYes yesYes
FileNotFoundException yesYes yesYes
DirectoryNotFoundException yesYes yesYes
DriveNotFoundException yesYes yesYes
PathTooLongException yesYes yesYes
OperationCanceledException yesYes yesYes
UnauthorizedAccessException yesYes yesYes
ArgumentException .NET Core 2.0 및 이전.NET Core 2.0 and earlier yesYes
NotSupportedException 아니요No yesYes
SecurityException 아니요No 제한된 신뢰만Limited trust only

IOException 처리Handling IOException

System.IO 네임스페이스에 있는 예외의 기본 클래스인 IOException은 미리 정의된 예외 유형에 매핑되지 않는 오류 코드에 대해서도 throw됩니다.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 작업에서 throw될 수 있습니다.This means that it can be thrown by any I/O operation.

중요

IOExceptionSystem.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부터 경로 정확성에 대한 유효성 검사(예: 경로에 잘못된 문자가 없는지 확인)가 제거되었으며, 런타임 시 자체 유효성 검사 코드가 아닌 운영 체제 오류 코드에서 매핑된 예외가 throw됩니다.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. 이 경우에 throw될 가능성이 큰 예외는 IOException이지만 다른 예외 유형도 throw될 수 있습니다.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