Partilhar via


Manipulando erros de E/S no .NET

Além das exceções que podem ser lançadas em qualquer chamada de método (como quando OutOfMemoryException um sistema está estressado ou devido a um NullReferenceException erro do programador), os métodos do sistema de arquivos .NET podem lançar as seguintes exceções:

Mapeando códigos de erro para exceções

Como o sistema de arquivos é um recurso do sistema operacional, os métodos de E/S no .NET Core e no .NET Framework encapsulam chamadas para o sistema operacional subjacente. Quando ocorre um erro de E/S no código executado pelo sistema operacional, o sistema operacional retorna informações de erro para o método de E/S .NET. Em seguida, o método traduz as informações de erro, normalmente na forma de um código de erro, em um tipo de exceção .NET. Na maioria dos casos, ele faz isso traduzindo diretamente o código de erro em seu tipo de exceção correspondente; Ele não executa nenhum mapeamento especial do erro com base no contexto da chamada de método.

Por exemplo, no sistema operacional Windows, uma chamada de método que retorna um código de erro de ERROR_FILE_NOT_FOUND (ou 0x02) mapeia para um FileNotFoundException, e um código de erro de ERROR_PATH_NOT_FOUND (ou 0x03) mapeia para um DirectoryNotFoundException.

No entanto, as condições precisas sob as quais o sistema operacional retorna códigos de erro específicos geralmente não estão documentadas ou estão mal documentadas. Como resultado, exceções inesperadas podem ocorrer. Por exemplo, como você está trabalhando com um diretório em vez de um arquivo, você esperaria que fornecer um caminho de diretório inválido para o DirectoryInfo construtor lançasse um DirectoryNotFoundExceptionarquivo . No entanto, também pode lançar um FileNotFoundException.

Tratamento de exceções em operações de E/S

Devido a essa dependência do sistema operacional, condições de exceção idênticas (como o erro de diretório não encontrado em nosso exemplo) podem resultar em um método de E/S lançando qualquer uma de toda a classe de exceções de E/S. Isso significa que, ao chamar APIs de E/S, seu código deve estar preparado para lidar com a maioria ou todas essas exceções, conforme mostrado na tabela a seguir:

Tipo de exceção .NET Core/.NET 5+ .NET Framework
IOException Sim Sim
FileNotFoundException Sim Sim
DirectoryNotFoundException Sim Sim
DriveNotFoundException Sim Sim
PathTooLongException Sim Sim
OperationCanceledException Sim Sim
UnauthorizedAccessException Sim Sim
ArgumentException .NET Core 2.0 e versões anteriores Sim
NotSupportedException No Sim
SecurityException No Apenas confiança limitada

Manipulação de IOException

Como a classe base para exceções no namespace, IOException também é lançada para qualquer código de erro que não mapeie System.IO para um tipo de exceção predefinido. Isso significa que ele pode ser lançado por qualquer operação de E/S.

Importante

Como IOException é a classe base dos outros tipos de exceção no System.IO namespace, você deve manipular em um catch bloco depois de manipular as outras exceções relacionadas a E/S.

Além disso, a partir do .NET Core 2.1, as verificações de validação quanto à correção do caminho (por exemplo, para garantir que caracteres inválidos não estejam presentes em um caminho) foram removidas e o tempo de execução lança uma exceção mapeada a partir de um código de erro do sistema operacional em vez de seu próprio código de validação. A exceção mais provável de ser lançada neste caso é um IOException, embora qualquer outro tipo de exceção também possa ser lançada.

Observe que, em seu código de tratamento de exceções, você sempre deve manipular o IOException último. Caso contrário, por ser a classe base de todas as outras exceções de E/S, os blocos catch de classes derivadas não serão avaliados.

No caso de um IOException, você pode obter informações de erro adicionais da propriedade IOException.HResult . Para converter o valor HResult em um código de erro Win32, remova os 16 bits superiores do valor de 32 bits. A tabela a seguir lista os códigos de erro que podem ser encapsulados em um IOExceptionarquivo .

HResult Constante Description
ERROR_SHARING_VIOLATION 32 O nome do arquivo está ausente ou o arquivo ou diretório está em uso.
ERROR_FILE_EXISTS 80 O ficheiro já existe.
ERROR_INVALID_PARAMETER 87 Um argumento fornecido para o método é inválido.
ERROR_ALREADY_EXISTS 183 O arquivo ou diretório já existe.

Você pode lidar com isso usando uma When cláusula em uma instrução catch, como mostra o exemplo a seguir.

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

Consulte também