Обработка ошибок ввода-вывода в .NET
Помимо тех исключений, которые могут быть созданы в любом вызове метода (например, 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 служат оболочками для соответствующих вызовов операционной системы. Если в коде, выполняемом операционной системой, возникает ошибка ввода-вывода, операционная система возвращает сведения об ошибке в метод ввода-вывода .NET. Затем этот метод преобразует сведения об ошибке, обычно представленные кодом ошибки, в соответствующий тип исключения .NET. В большинстве случаев код ошибки напрямую определяет нужный тип исключения. Метод не выполняет никакой дополнительной обработки в соответствии с контекстом вызова метода.
Например, при вызове метода в операционной системе Windows код ошибки ERROR_FILE_NOT_FOUND
(или 0x02) преобразуется в исключение FileNotFoundException, а код ошибки ERROR_PATH_NOT_FOUND
(или 0x03) — в DirectoryNotFoundException.
К сожалению, точные условия возникновения определенных кодов ошибок в операционной системе часто не документируются или документируются в недостаточном объеме. Это означает, что возможны непредвиденные исключения. Например, при работе с каталогом логично ожидать, что передача недопустимого пути в конструктор DirectoryInfo приведет к созданию исключения DirectoryNotFoundException. Но в этой ситуации может создаваться и FileNotFoundException.
Обработка исключений при операциях ввода-вывода
По причине зависимости от операционной системы иногда идентичные условия (например, отсутствие указанного каталога) могут создавать в методах ввода-вывода любое исключение из класса ввода-вывода. Это означает, что при вызове интерфейсов API ввода-вывода ваш код должн быть готов обработать все такие исключения или большую их часть, как показано в следующей таблице:
Тип исключения | .NET Core и .NET 5 или более поздней версии | .NET Framework |
---|---|---|
IOException | Да | Да |
FileNotFoundException | Да | Да |
DirectoryNotFoundException | Да | Да |
DriveNotFoundException | Да | Да |
PathTooLongException | Да | Да |
OperationCanceledException | Да | Да |
UnauthorizedAccessException | Да | Да |
ArgumentException | .NET Core 2.0 или более ранняя версия | Да |
NotSupportedException | No | Да |
SecurityException | Нет | Только для ограниченного доверия |
Обработка IOException
IOException является базовым классом для исключений в пространстве имен System.IO и создается для любого кода ошибки, который не имеет сопоставления с определенным типом исключения. Это означает, что оно может появиться в любой операции ввода-вывода.
Внимание
Так как IOException является базовым классом для других типов исключений в пространстве имен System.IO, его нужно обрабатывать в блоке catch
после обработки других исключений, связанных с вводом-выводом.
Кроме того, в версии .NET Core 2.1 не применяется проверка правильности пути (например, отсутствия недопустимых символов в пути), и теперь среда выполнения создает исключение, сопоставленное с кодом ошибки операционной системы, а не результат проверки в собственном коде. В такой ситуации чаще всего создается исключение IOException, но вы можете столкнуться с любым другим типом исключений.
Обратите внимание, что в коде обработки исключений IOException всегда нужно обрабатывать последним. Иначе блоки catch для производных классов не проверяются, ведь это исключение является базовым классом для всех остальных.
В случае с IOException дополнительные сведения об ошибке можно получить из свойства IOException.HResult. Чтобы преобразовать значение HResult в код ошибки Win32, отбросьте верхние 16 бит из 32-разрядного значения. В приведенной ниже таблице перечислены коды ошибок, которые могут быть заключены в IOException.
HResult | Константа | Description |
---|---|---|
ERROR_SHARING_VIOLATION | 32 | Отсутствует имя файла, или файл или каталог уже используется. |
ERROR_FILE_EXISTS | 80 | Файл уже существует. |
ERROR_INVALID_PARAMETER | 87 | Методу передан недопустимый аргумент. |
ERROR_ALREADY_EXISTS | 183 | Файл или каталог уже существует. |
Для обработки этих исключений можно применить предложение When
в инструкции catch, как показано в приведенном ниже примере.
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