Ausnahme-Behandlung in C# (Exception-Handling)

Ein Programm, welches mit einer höheren Programmiersprache entwickelt wurde, interagiert mit seiner Umwelt, neben anderen Programmen vor allem mit dem Betriebssystem, über dieses auch mit der Hardware.

Während das Programm bis in das kleinste Detail durchdacht und nach unzähligen Tests auch (scheinbar) völlig fehlerfrei funktioniert, kann es bei der Interaktion zwischen dem Programm mit seiner Umwelt zu Problemen kommen.

Beispielsweise könnte es beim Öffnen einer Datei zu einem Fehler kommen, die Datei lässt sich nicht öffnen (obwohl das Programm vorher zumindest die Existenz der Datei festgestellt hatte). Möglicherweise lässt sich eine Datei auch nicht speichern, da der Speicherplatz nicht ausreicht, das Speichermedium entfernt wurde oder defekt ist. Nur einige von sehr vielen Beispielen von sogenannten Exceptions.
Einige Exceptions lassen sich bis zu einem bestimmten Grad vorbeugen; Beispielsweise lässt sich unmittelbar vor der Beanspruchung von Speicherplatz prüfen, ob der Speicherplatz ausreicht. Ob der Speicherplatz dann aber bei der Speicherung tatsächlich vorhanden ist und nicht schon von einem anderen Vorgang belegt wurde, ist erst bei der Speicherung feststellbar.

Mit einem bestimmten System lassen sich solche Fehler zwar nicht verhindern, jedoch behandeln.
Software-Entwickler sprechen hierbei von Fehlerbehandlung oder (besser:) Ausnahmebehandlungen, also die Behandlung von Ausnahmen (Exceptions).

Mit einer Ausnahmebehandlung, wie sie z.B. in C++, Java und C# möglich ist, können diese Fehler umgangen werden, so dass Programm nur die fehlerträchtige Funktion beendet, aber dennoch nicht komplett abstürzt. Das .NET Framework von Microsoft bietet beispielsweise ein einheitliches Fehlermanagement mit der Möglichkeit zur Ausnahmebehandlung.

Was kann mit der Ausnahmebehandlung erreicht werden?

Im Grund bezweckt die Fehlerbehandlung, dass ein Programm immer stabil läuft. Frühere Programme, welche über keine ausgereifte Fehlerbehandlung verfügen, stürzten häufig komplett ab, wenn eine Funktionalität (z.B. das Schreiben auf einen Datenträger) einen Fehler erzeugte. Dies ist besonders ärgerlich, wenn im Programm (bzw. im Arbeitsspeicher) noch nicht gesicherte Daten liegen, welche beim Programmabsturz komplett verloren gehen.

Wo können diese Fehler entstehen?

Im Laufzeitsystem: z.B. wenn ein Stack „gesprengt“ wurde (StackOverflow) oder auch sogenannte Nullreferenzen (Arrayfeld mit bestimmten Index nicht vorhanden etc.)

Bei der Benutzung von Bibliotheksklassen: z.B. wenn das Lesen aus einer Datei über eine Methode aus einer Bibliotheksklasse fehlschlägt

Im eigenen Code: Es ist möglich Exceptions nach eigenem Wunsch auszulösen und auch eigene Exceptions zu definieren und diese auszulösen

Hinweis: Mit Exception-Behandlung sollen keine Programmfehler “ausgebügelt” werden, Programmfehler bzw. Fehler im Programmalgorithmus sollen behoben, nicht mit einer Exception-Behandlung umgangen werden.

Beispiel: Exception in C#

Eine Klasse Exception definiert die Exception, eine Exception ist eine Instanz dieser Klasse.

Neben der allgemeinen Exception gibt es spezielle Exceptions, welchen nur einen besonderen Fehler abfangen.

Eine spezielle Exception ist immer mit Exception benannt, Beispiele:

  • FileNotFoundException
  • IndexOutOfRangeException
  • OutOfMemoryException

Aufbau der Ausnahmebehandlung:

Die Ausnahmebehandlung wird mit nur drei Schlüsselwörtern gegliedert: try, catch und finally

Try („Versuch“ bzw. „versuche“)

Im Try-Block wird der Quellcode eingeschleust, welcher in Verdacht steht, Exceptions auszulösen.

[csharp] try
{
//… der Code, welche Ausnahmen erzeugen könnte
}
[/csharp]

Catch („Abfangen“)

Im Catch-Block wird eine Exception behandelt.

[csharp] catch (Exception e) // allgemeine Exception (irgend eine!)
{
// Behandlung der Ausnahme, z.B. Ausgabe der Fehlermeldung
}
[/csharp]

Der Catch-Block kann jede Exception abfangen oder sich auf eine spezielle Exception beschränken

[csharp] catch (FileNotFoundException fnfe) // Behandlung der FileNotFoundException (nur diese!)
{
// Behandlung der speziellen Ausnahme, z.B. Ausgabe an den User, was schief gelaufen sein könnte
}
[/csharp]

Wenn jedoch nur bestimmte Exceptions behandelt werden, jedoch eine Exception auftritt, welche durch keinen Catch-Block abgefangen wird, wird die Exception in das nächst höhere Level weitergegeben (bis hin zum Betriebssystem) und zumindest die Methode, wenn nicht das gesamte Programm, abgebrochen.

Finally („abschließend“)

Ein Programmierer hat für die Sicherheit seines Programms Sorge zu tragen und so einige Feinheiten bei der Programmierung zu beachten.

So sind häufig Anweisungen auszuführen, die unbedingt ausgeführt werden müssen, „komme was wolle“.

Beispielweise sollte ein Stream (z.B. zu einer Datei) immer geschlossen werden, nach der Benutzung.

[csharp] StreamReader sr;

// Benutzung

sr.close();
[/csharp]

Der Stream soll immer geschlossen werden, unbedingt aber auch, wenn etwas bei der Benutzung des Streams schief läuft.

Hierfür gibt es den Finally-Block, welcher dazu dient, eine sichere Zone für kritische Aufgabe zu bilden. Anweisungen, welche in jedem Fall auszuführen sind, werden in den Finally-Block platziert.

[csharp] finally
{
if (sr != null)
{
sr.Close();
}
}
[/csharp]

Der Zweck von Finally ist sicher leicht nach zu vollziehen, dennoch kommt schnell die Frage auf, warum der Stream nicht einfach hinter/unter der Catch-Anweisung platziert wird; dann würde der Code ebenfalls ausgeführt werden, egal ob eine FileNotFoundException ausgelöst wurde oder nicht.

Das ist soweit richtig, allerdings ist der Finally-Block eine speziell sichere Zone, welcher unbedingt vor Verlassen der Methode ausgeführt wird.
Wenn beispielsweise eine andere als die FileNotFoundException auftritt, würde die Methode sofort stoppen und der Stream nicht geschlossen werden. Der Finally-Block würde aber in jedem Fall ausgeführt werden.

Weitergabe einer Exception:

Es kann auch notwendig sein, dass eine Methode abgefangen und behandelt wird, dann aber doch trotzdem an die nächst höhere Ebene im Programm oder gar an das Betriebssystem weitergereicht wird, damit die Exception auch auf höherer Ebene behandelt werden kann. Dies ist mit dem Schlüsselwort throw möglich.

[csharp] catch (Exception e)
{
// Fehlerbehandlung

MessageBox.Show(e.Message);

throw e; // Weiterreichung der Exception
}
[/csharp]

Mit dem Schlüsselwort throw wird die Exception neu geworfen und dadurch die Weiterleitung nach der erstmaligen Behandlung erreicht.

Auf diese Art lässt sich auch irgend eine definierte Exception auslösen, zu jeder Zeit.

[csharp] public void GiveException()
{
throw new NoNullAllowedException();
}
[/csharp]

Komplette Beispiele:

1. Beispiel – Datei lesen und die FileNotFoundException

Die Methode FileIsReady() prüft die Zugriffsbereitschaft genau, indem sie ausgelesen wird.

[csharp] public bool FileIsReady(string path)
{
bool isReady = true; // zugriffsbereit (optimistisch)

try
{
sr = new StreamReader(path);
string line;

while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
catch (FileNotFoundException)
{
isReady = false; // doch nicht zugriffsbereit
Console.WriteLine(“File does not exist !?”);
}
finally
{
if (sr != null)
{
sr.Close();
}
}

return isReady;
}
[/csharp]

Hinweis: In dem Beispiel wird nur die FileNotFoundException behandelt. Ein Dateizugriff bzw. der Versuch eine Datei zu öffnen, kann jedoch auch zu weiteren Exceptions führen.

Der Test, ob eine Datei zugriffsbereit ist, ist in der Praxis eher unnötig, da sich die Datei, wenn sie wirklich erst benötigt wird, schon wieder in einem anderen Status befinden könnte. Daher wird i.d.R. auf die Datei zugegriffen wenn es nötig wird und genau dann auf eventuell auftretende Ausnahmen eingegangen.

2. Beispiel – Zugriff auf ein Array und die IndexOutOfRangeException

Die TestClass stellt die Methode SetNull() zur Verfügung, welche ein eigenes Array anspricht. Die Funktion setzt alle Felder im Array von (Parameter) start bis end auf den Wert 0.

[csharp] ///
/// Diese Klasse dient der Produktion von Exceptions
///
public class TestClass
{
public TestClass()
{
}

/// Setzt alle Werte eines eigenen Arrays vom Start-Index bis Ziel-Index
/// auf den Wert 0
/// Bei zu hohem Index wird so die Exception “IndexOutOfRangeException”
/// ausgelöst
public void SetNull(int start, int end)
{
int[] numbers = new int[100];

for (int i = start; i <= end; i++) { numbers[i] = 0; Console.WriteLine(i); } } } [/csharp] Die Klasse Form1 instanziiert die Klasse TestClass und führt deren Methode SetNull() aus. Dabei wird der Wert 500 als Parameter end übergeben, welcher viel zu groß ist und so eine IndexOutOfRangeException hervorruft.

[csharp] public partial class Form1 : Form
{
public Form1()
{
// Testklasse instanziieren
TestClass tc = new TestClass();

// versuche…..
try
{ // … die Methode auszuführen
tc.SetNull(80, 500);
}
// Falls Fehlerfall 1 (Index zu hoch)…
catch (IndexOutOfRangeException iore)
{
// …gebe eine Fehlermeldung aus
Console.WriteLine(“Index Out of Range :(“);
}
// Falls Fehlerfall 2 (irgend ein Fehler)…
catch (Exception e)
{
// …gebe die Fehlermeldung aus
Console.WriteLine(e.Message);
}

// …. weiter geht`s mit anderen Aufgaben…
}
[/csharp]

Hinweise: In der Praxis sollte die Methode SetNull() natürlich mit eine If-Bedingung feststellen, ob die Größe des Array ausreicht. So ließe sich die IndexOutOfRangeException vermeiden!

1 Gedanke zu “Ausnahme-Behandlung in C# (Exception-Handling)

Kommentare sind geschlossen.