Обработка ошибок ввода-вывода в .NETHandling 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 служат оболочками для соответствующих вызовов операционной системы.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. Если в коде, выполняемом операционной системой, возникает ошибка ввода-вывода, операционная система возвращает сведения об ошибке в метод ввода-вывода .NET.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.

Обработка исключений при операциях ввода-выводаException handling in I/O operations

По причине зависимости от операционной системы иногда идентичные условия (например, отсутствие указанного каталога) могут создавать в методах ввода-вывода любое исключение из класса ввода-вывода.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. Это означает, что при вызове интерфейсов 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

Обработка IOExceptionHandling IOException

IOException является базовым классом для исключений в пространстве имен System.IO и создается для любого кода ошибки, который не имеет сопоставления с определенным типом исключения.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. Это означает, что оно может появиться в любой операции ввода-вывода.This means that it can be thrown by any I/O operation.

Важно!

Так как IOException является базовым классом для других типов исключений в пространстве имен System.IO, его нужно обрабатывать в блоке 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. Иначе блоки 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, отбросьте верхние 16 бит из 32-разрядного значения.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.

Для обработки этих исключений можно применить предложение When в инструкции catch, как показано в приведенном ниже примере.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:\nError code: " +
                              $"{e.HResult And &h0000FFFF}\nMessage: {e.Message}")
        End Try
        Return Nothing
    End Function
End Module

См. такжеSee also