Behandeln von E/A-Fehlern in .NET
Über die Ausnahmen hinaus, die bei jedem Methodenaufruf ausgelöst werden können (wie etwa eine OutOfMemoryException, wenn ein System unter Stress steht, oder eine NullReferenceException aufgrund eines Programmierfehlers), können Methoden des .NET-Dateisystems die folgenden Ausnahmen auslösen:
- System.IO.IOException, die Basisklasse aller System.IO-Ausnahmetypen. Sie wird für Fehler ausgelöst, deren Rückgabecodes aus dem Betriebssystem sich nicht direkt anderen Ausnahmetypen zuordnen lassen.
- System.IO.FileNotFoundException.
- System.IO.DirectoryNotFoundException.
- DriveNotFoundException.
- System.IO.PathTooLongException.
- System.OperationCanceledException.
- System.UnauthorizedAccessException.
- System.ArgumentException, die für ungültige Zeichen in Pfaden für .NET Framework und .NET Core 2.0 sowie frühere Versionen ausgelöst wird.
- System.NotSupportedException, die für ungültige Doppelpunkte in .NET Framework ausgelöst wird.
- System.Security.SecurityException, die für Anwendungen ausgelöst wird, die mit eingeschränkter Vertrauenswürdigkeit ausgeführt werden und denen lediglich die erforderlichen Berechtigungen für .NET Framework fehlen. (Volle Vertrauenswürdigkeit stellt in .NET Framework den Standard dar.)
Zuordnen von Fehlercodes zu Ausnahmen
Da das Dateisystem eine Betriebssystemressource ist, umschließen E/A-Methoden sowohl in .NET Core als auch in .NET Framework Aufrufe an das zugrundeliegende Betriebssystem. Wenn in Code, der vom Betriebssystem ausgeführt wird, ein E/A-Fehler auftritt, gibt das Betriebssystem Fehlerinformationen zu der .NET-E/A-Methode zurück. Die Methode übersetzt dann die Fehlerinformationen, normalerweise in Form eines Fehlercodes, in einen .NET-Ausnahmetyp. In den meisten Fällen erfolgt dies durch direktes Übersetzen des Fehlercodes in den entsprechenden Ausnahmetyp; es wird keine besondere Zuordnung des Fehlers auf der Grundlage des Kontexts des Methodenaufrufs vorgenommen.
Beispielsweise wird im Windows-Betriebssystem ein Methodenaufruf, der den Fehlercode ERROR_FILE_NOT_FOUND
(oder 0x02) zurückgibt, einer FileNotFoundException zugeordnet, und der Fehlercode ERROR_PATH_NOT_FOUND
(oder 0x03) wird einer DirectoryNotFoundException zugeordnet.
Die genauen Umstände, unter denen das Betriebssystem bestimmte Fehlercodes zurückgibt, sind aber häufig undokumentiert oder schlecht dokumentiert. Daher können unerwartete Ausnahmen auftreten. Wenn Sie beispielsweise mit einem Verzeichnis anstelle einer Datei arbeiten, könnten Sie annehmen, dass die Angabe eines falschen Verzeichnispfads für den DirectoryInfo-Konstruktor eine DirectoryNotFoundException auslöst. Es kann aber ebenso gut eine FileNotFoundException auslösen.
Ausnahmebehandlung in E/A-Vorgängen
Aufgrund dieses Rückgriffs auf das Betriebssystem können identische Ausnahmebedingungen (wie etwa der Fehler des nicht gefundenen Verzeichnisses in unserem Beispiel) dazu führen, dass eine E/A-Methode irgendeine aus der gesamten Klasse der E/A-Ausnahmen auslöst. Das bedeutet, dass beim Aufrufen von E/A-APIs Ihr Code in der Lage sein sollte, die meisten oder alle diese Ausnahmen zu behandeln, wie in der folgenden Tabelle dargestellt:
Ausnahmetyp | .NET Core/.NET 5 oder höher | .NET Framework |
---|---|---|
IOException | Ja | Ja |
FileNotFoundException | Ja | Ja |
DirectoryNotFoundException | Ja | Ja |
DriveNotFoundException | Ja | Ja |
PathTooLongException | Ja | Ja |
OperationCanceledException | Ja | Ja |
UnauthorizedAccessException | Ja | Ja |
ArgumentException | .NET Core 2.0 und früher | Ja |
NotSupportedException | Keine | Ja |
SecurityException | Nein | Eingeschränkte Vertrauenswürdigkeit |
Behandlung von IOException
Als Basisklasse für Ausnahmen im Namespace System.IO wird IOException auch für jeden Fehlercode ausgelöst, der sich keinem vordefinierten Ausnahmetyp zuordnen lässt. Das bedeutet, dass sie von jedem E/A-Vorgang ausgelöst werden kann.
Wichtig
Da IOException die Basisklasse der anderen Ausnahmetypen im Namespace System.IO ist, sollten Sie sie nach der Behandlung der anderen Ausnahmen im E/A-Zusammenhang in einem catch
-Block behandeln.
Seit .NET Core 2.1 wurden darüber hinaus die Gültigkeitsprüfungen von Pfaden (um beispielsweise sicherzustellen, dass keine ungültigen Zeichen in einem Pfad vorhanden sind) entfernt, und die Runtime löst eine Ausnahme aus, die von einem Fehlercode des Betriebssystems anstelle seines eigenen Validierungscodes zugeordnet wurde. In diesem Fall ist die Ausnahme, die mit der größten Wahrscheinlichkeit ausgelöst wird, IOException; allerdings könnte auch jeder andere Ausnahmetyp ausgelöst werden.
Beachten Sie, dass Sie in Ihrem Fehlerbehandlungscode die IOException immer zuletzt behandeln sollten. Da sie die Basisklasse aller anderen EA-Ausnahmen bildet, werden sonst die Catch-Blöcke von abgeleiteten Klassen nicht ausgewertet.
Im Fall einer IOException können Sie zusätzliche Fehlerinformationen aus der Eigenschaft IOException.HResult abrufen. Um den HResult-Wert in einen Win32-Fehlercode zu konvertieren, entfernen Sie die oberen 16 Bits des 32-Bit-Werts. In der folgenden Tabelle sind Fehlercodes aufgelistet, die von einer IOException umschlossen werden können.
HResult | Konstante | Beschreibung |
---|---|---|
ERROR_SHARING_VIOLATION | 32 | Der Dateiname fehlt, oder die Datei oder das Verzeichnis wird verwendet. |
ERROR_FILE_EXISTS | 80 | Die Datei ist bereits vorhanden. |
ERROR_INVALID_PARAMETER | 87 | Ein der Methode übergebenes Argument ist ungültig. |
ERROR_ALREADY_EXISTS | 183 | Die Datei oder das Verzeichnis ist bereits vorhanden. |
Sie können diese mithilfe einer When
-Klausel in einer catch-Anweisung behandeln, wie im folgenden Beispiel dargestellt.
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