Control de errores de E/S en .NET

Además de las excepciones que se pueden producir en cualquier llamada al método (como OutOfMemoryException cuando un sistema está sobrecargado o NullReferenceException debido a errores de programador), los métodos del sistema de archivos de .NET pueden producir las excepciones siguientes:

Asignación de códigos de error a excepciones

Dado que el sistema de archivos es un recurso del sistema operativo, los métodos de E/S de .NET Core y .NET Framework encapsulan llamadas en el sistema operativo subyacente. Cuando se produce un error de E/S en el código ejecutado por el sistema operativo, el sistema operativo devuelve información de error para el método de E/S de .NET. El método después traduce la información de error, normalmente en forma de un código de error, en un tipo de excepción de .NET. En la mayoría de los casos, lo hace convirtiendo directamente el código de error en su correspondiente tipo de excepción; no realiza ninguna asignación especial del error en función del contexto de la llamada al método.

Por ejemplo, en el sistema operativo Windows, una llamada al método que devuelve un código de error ERROR_FILE_NOT_FOUND, o bien 0 x 02, se asigna a FileNotFoundException, y un código de error ERROR_PATH_NOT_FOUND, o bien 0 x 03, se asigna a DirectoryNotFoundException.

Sin embargo, las condiciones precisas en las que el sistema operativo devuelve códigos de error concretos no suelen estar documentadas o, en caso de estarlo, la documentación suele ser escasa. Como resultado, pueden producirse excepciones inesperadas. Por ejemplo, puesto que está trabajando con un directorio en lugar de un archivo, cabría esperar que proporcionar una ruta de acceso de directorio no válida para el constructor DirectoryInfo produzca DirectoryNotFoundException. Sin embargo, también puede producir FileNotFoundException.

Control de excepciones en operaciones de E/S

Debido a esta dependencia en el sistema operativo, condiciones de excepción idénticas (como el caso en que el directorio no encontró el error en nuestro ejemplo) pueden dar lugar a que un método de E/S genere cualquier clase entera de excepciones de E/S. Esto significa que, al llamar a API de E/S, el código debe prepararse para controlar la mayoría de estas excepciones o todas, como se muestra en la siguiente tabla:

Tipo de excepción .NET Core, y .NET 5 y versiones posteriores .NET Framework
IOException
FileNotFoundException
DirectoryNotFoundException
DriveNotFoundException
PathTooLongException
OperationCanceledException
UnauthorizedAccessException
ArgumentException .NET Core 2.0 y versiones anteriores
NotSupportedException No
SecurityException No Confianza limitada solo

Control de excepciones de E/S

Como la clase base para las excepciones en el espacio de nombres System.IO, también se produce IOException para cualquier código de error que no se asigna a un tipo de excepción predefinido. Esto significa que se puede iniciar con cualquier operación de E/S.

Importante

Dado que IOException es la clase base de los otros tipos de excepción en el espacio de nombres System.IO, se debe controlar en un bloque catch después de haber controlado las otras excepciones relacionadas con E/S.

Además, a partir de .NET Core 2.1, se han quitado las comprobaciones de validación de la exactitud de la ruta de acceso (por ejemplo, para asegurarse de que los caracteres no válidos no están presentes en una ruta de acceso), y el tiempo de ejecución produce una excepción que se asigna desde un código de error del sistema operativo y no desde su propio código de validación. La excepción que es más probable que se produzca en este caso es IOException, aunque también podría generarse cualquier otro tipo de excepción.

Tenga en cuenta que, en el código de control de excepciones, siempre debe controlar la última clase IOException. De lo contrario, como se trata de la clase base de todas las excepciones de E/S, los bloques catch de las clases derivadas no se evaluarán.

Si se trata de IOException, puede obtener información de error adicional de la propiedad IOException.HResult. Para convertir el valor HResult en un código de error de Win32, quite los 16 bits superiores del valor de 32 bits. En la tabla siguiente se enumeran los códigos de error que pueden encapsularse en IOException.

HResult Constante Descripción
ERROR_SHARING_VIOLATION 32 Falta el nombre de archivo, o el archivo o directorio está en uso.
ERROR_FILE_EXISTS 80 El archivo ya existe.
ERROR_INVALID_PARAMETER 87 El argumento proporcionado al método no es válido.
ERROR_ALREADY_EXISTS 183 El archivo o directorio ya existe.

Puede controlarlos con una cláusula When en una instrucción catch, como se muestra en el ejemplo siguiente.

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

Vea también