Gestione degli errori di I/O in .NETHandling I/O errors in .NET

Oltre alle eccezioni che possono essere generate in qualsiasi chiamata a un metodo (ad esempio, OutOfMemoryException quando un sistema è in sovraccarico o NullReferenceException a causa di un errore del programmatore), i metodi del file system .NET possono generare le eccezioni seguenti: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 dei codici di errore alle eccezioniMapping error codes to exceptions

Poiché il file system è una risorsa del sistema operativo, i metodi di I/O sia in .NET Core che in .NET eseguono il wrapping delle chiamate nel sistema operativo sottostante.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. Quando si verifica un errore di I/O nel codice eseguito dal sistema operativo, il sistema operativo restituisce le informazioni sull'errore al metodo di I/O di .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. Il metodo converte quindi le informazioni sull'errore, in genere in formato codice di errore, in un tipo di eccezione .NET.The method then translates the error information, typically in the form of an error code, into a .NET exception type. Nella maggior parte dei casi, esegue questa operazione convertendo direttamente il codice di errore nel tipo di eccezione corrispondente, senza eseguire mapping specifici dell'errore basati sul contesto della chiamata al metodo.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.

Nel sistema operativo Windows, ad esempio, una chiamata a un metodo che restituisce un codice di errore ERROR_FILE_NOT_FOUND (o 0x02) esegue il mapping a FileNotFoundException e un codice di errore ERROR_PATH_NOT_FOUND (o 0x03) esegue il mapping a 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.

Tuttavia, le condizioni esatte in cui il sistema operativo restituisce determinati codici di errore spesso sono poco o per nulla documentate.However, the precise conditions under which the operating system returns particular error codes is often undocumented or poorly documented. Possono quindi verificarsi eccezioni impreviste.As a result, unexpected exceptions can occur. Poiché ad esempio si usa una directory invece di un file, se si fornisse un percorso di directory non valido al costruttore DirectoryInfo, dovrebbe essere generata un'eccezione 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 constructor throws a DirectoryNotFoundException. ma potrebbe anche venire generata un'eccezione FileNotFoundException.However, it may also throw a FileNotFoundException.

Gestione delle eccezioni nelle operazioni di I/OException handling in I/O operations

A causa di questa dipendenza dal sistema operativo, con condizioni di eccezione identiche (come l'errore di directory non trovata dell'esempio) un metodo di I/O può generare una qualsiasi tra tutte le eccezioni di I/O della classe.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. Pertanto, quando si chiamano le API di I/O, il codice deve essere preparato per gestire tutte o quasi tutte le eccezioni, come illustrato nella tabella seguente: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:

Tipo di eccezioneException 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 e versioni precedenti.NET Core 2.0 and earlier Yes
NotSupportedException NoNo Yes
SecurityException NoNo Solo attendibilità limitataLimited trust only

Gestione di IOExceptionHandling IOException

In quanto classe di base per le eccezioni nello spazio dei nomi System.IO, viene generata un'eccezione IOException anche per i codici di errore che non eseguono il mapping a un tipo di eccezione predefinito.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. Può essere quindi generata da qualsiasi operazione di I/O.This means that it can be thrown by any I/O operation.

Importante

Poiché IOException è la classe di base degli altri tipi di eccezioni nello spazio dei nomi System.IO, è consigliabile gestire un blocco catch dopo aver gestito le altre eccezioni relative alle operazioni di I/O.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.

Inoltre, a partire da .NET Core 2.1, i controlli di convalida della correttezza del percorso (ad esempio, per assicurarsi che in un percorso non siano presenti caratteri non validi) sono stati rimossi e il runtime genera un'eccezione di cui viene eseguito il mapping da un codice di errore del sistema operativo invece che dal proprio codice di convalida.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. In questo caso l'eccezione che viene generata con maggior probabilità è IOException, ma potrebbe essere generato qualsiasi altro tipo di eccezione.The most likely exception to be thrown in this case is an IOException, although any other exception type could also be thrown.

Si noti che nel codice di gestione dell'eccezione, si dovrà gestire sempre per ultima IOException.Note that, in your exception handling code, you should always handle the IOException last. In caso contrario, poiché si tratta della classe di base di tutte le altre eccezioni di I/O, i blocchi catch di classi derivate non verranno valutati.Otherwise, because it is the base class of all other IO exceptions, the catch blocks of derived classes will not be evaluated.

Nel caso di IOException, è possibile ottenere altre informazioni sull'errore dalla proprietà IOException.HResult.In the case of an IOException, you can get additional error information from the IOException.HResult property. Per convertire il valore HResult in un codice di errore Win32, si rimuovono i 16 bit superiori del valore da 32 bit.To convert the HResult value to a Win32 error code, you strip out the upper 16 bits of the 32-bit value. La tabella seguente elenca i codici di errore di cui potrebbe essere eseguito il wrapping in un'eccezione IOException.The following table lists error codes that may be wrapped in an IOException.

HResultHResult CostanteConstant DescrizioneDescription
ERROR_SHARING_VIOLATIONERROR_SHARING_VIOLATION 3232 Il nome del file è mancante oppure il file o la directory è in uso.The file name is missing, or the file or directory is in use.
ERROR_FILE_EXISTSERROR_FILE_EXISTS 8080 File già esistente.The file already exists.
ERROR_INVALID_PARAMETERERROR_INVALID_PARAMETER 8787 Un argomento fornito al metodo non è valido.An argument supplied to the method is invalid.
ERROR_ALREADY_EXISTSERROR_ALREADY_EXISTS 183183 File o directory già esistente.The file or directory already exists.

È possibile gestirli usando una clausola When in un'istruzione catch, come illustrato nell'esempio seguente.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

Vedere ancheSee also