Gestione degli errori di I/O 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:
- System.IO.IOException, la classe di base di tutti i tipi di eccezioni System.IO. Viene generata per gli errori i cui codici restituiti dal sistema operativo non eseguono il mapping diretto a nessun altro tipo di eccezione.
- System.IO.FileNotFoundException.
- System.IO.DirectoryNotFoundException.
- DriveNotFoundException.
- System.IO.PathTooLongException.
- System.OperationCanceledException.
- System.UnauthorizedAccessException.
- System.ArgumentException, generata per i caratteri non validi nel percorso in .NET Framework e in .NET Core 2.0 e versioni precedenti.
- System.NotSupportedException, generata per i caratteri due punti non validi in .NET Framework.
- System.Security.SecurityException, generata per le applicazioni in esecuzione con attendibilità limitata senza le autorizzazioni necessarie solo in .NET Framework. L'attendibilità totale è l'impostazione predefinita in .NET Framework.
Mapping dei codici di errore alle eccezioni
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. 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. Il metodo converte quindi le informazioni sull'errore, in genere in formato codice di errore, in un tipo di eccezione .NET. 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.
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.
Tuttavia, le condizioni esatte in cui il sistema operativo restituisce determinati codici di errore spesso sono poco o per nulla documentate. Possono quindi verificarsi eccezioni impreviste. 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, ma potrebbe anche venire generata un'eccezione FileNotFoundException.
Gestione delle eccezioni nelle operazioni di I/O
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. 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:
Tipo di eccezione | .NET Core/.NET 5+ | .NET Framework |
---|---|---|
IOException | Sì | Sì |
FileNotFoundException | Sì | Sì |
DirectoryNotFoundException | Sì | Sì |
DriveNotFoundException | Sì | Sì |
PathTooLongException | Sì | Sì |
OperationCanceledException | Sì | Sì |
UnauthorizedAccessException | Sì | Sì |
ArgumentException | .NET Core 2.0 e versioni precedenti | Sì |
NotSupportedException | No | Sì |
SecurityException | No | Solo attendibilità limitata |
Gestione di 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. Può essere quindi generata da qualsiasi operazione di I/O.
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.
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 questo caso l'eccezione che viene generata con maggior probabilità è IOException, ma potrebbe essere generato qualsiasi altro tipo di eccezione.
Si noti che nel codice di gestione dell'eccezione, si dovrà gestire sempre per ultima IOException. 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.
Nel caso di IOException, è possibile ottenere altre informazioni sull'errore dalla proprietà IOException.HResult. Per convertire il valore HResult in un codice di errore Win32, si rimuovono i 16 bit superiori del valore da 32 bit. La tabella seguente elenca i codici di errore di cui potrebbe essere eseguito il wrapping in un'eccezione IOException.
HResult | Costante | Descrizione |
---|---|---|
ERROR_SHARING_VIOLATION | 32 | Il nome del file è mancante oppure il file o la directory è in uso. |
ERROR_FILE_EXISTS | 80 | File già esistente. |
ERROR_INVALID_PARAMETER | 87 | Un argomento fornito al metodo non è valido. |
ERROR_ALREADY_EXISTS | 183 | File o directory già esistente. |
È possibile gestirli usando una clausola When
in un'istruzione catch, come illustrato nell'esempio seguente.
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