Site logo
Site logo
Programmieren aus Leidenschaft
Programmieren aus Leidenschaft

Letzte Haltestelle: Exception

Programme stürzen ab, das ist nun mal so.
Natürlich kann man sich gegen den Fehlerfall schützen. Dafür gibt es in C# "try"- und "catch"-Befehle. Wichtige Befehle die einem geübten Programmieren vertraut sein sollten. Aber trotzdem können sich immer wieder Fehler einschleichen, die man nicht erwartet. Und das an Stellen im Programm, die unkritisch sein sollten. Die Gründe hierfür sind vielfältig aber das Resultat immer gleich. Das Programm stützt ab.
Dies mag schon tragisch genug sein, viel schlimmer ist es, dass man oftmals nicht weiß, an welcher Stelle das Programm den einen Fehler verursacht hat. Dagegen kann man aber etwas tun. Fehler, auch als Ausnahme oder Exceptions bekannt, können abgefangen und abgefragt werden. In der Regel passiert das in der Methode, in der der Fehler auftritt. Hat diese Methode keine Fehlerbehandlung, wird die Exception an die Methode weitergereicht, welche die Methode mit dem Fehler aufgerufen hat. Gibt es auch dort keine Fehlerbehandlung wird wieder nach oben weitergereicht, bis die Exception schließlich dort ankommt, wo alles begann.

In C#-Programmen ist das die Main Methode. Hier läuft alles zusammen und hier sollte auch eine Fehlerbehandlung sein, die dann aktiv wird, wenn es zuvor keine andere Fehlerbehandlung gab. Das könnte etwas so aussehen:
[STAThread]
static void Main()
{
   try
   {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run(new MainForm());
      }
   catch(Exception ex)
   {
      MessageBox.Show(ex.Message + "\n\n"+ ex.TargetSite.ReflectedType +"."+ ex.TargetSite.Name, "Oops!...Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error);
   }
}
Die Exception selbst ist ein Objekt mit verschiedenen Membervariablen. Lässt man sich die in einer Messagebox anzeigen, ist es viel leichter dem Fehler auf die Spur zu kommen und das Programm beendet sich nicht mehr kommentarlos.
Man sollte nicht verschweigen, das sich so ein try/catch-Block rund um die main-Methode durchaus negativ auf die Performace der Anwendung auswirken kann. Während der Entwicklung und bei der Fehlersuche kann es aber sehr nützlich sein.
Aber es gibt auch elegantere Vorgehensweisen, als die im letzen Beispiel gezeigte, mit den Fehlern eines Programmes umzugehen. Dazu muss man sich aber erst ein Mal vor Augen halten, was ein Fehler eigendlich ist. Auch eine Exception ist ein Ereignis, welches innerhalb eines Programmes stattfindet. Und so ist es nicht ungewöhnlich, dass man sich auch an dieses Ereignis, eine ThreadException, mit einer delegate-Methode anhängen kann.

Eine so angepasste Main-Methode könnte zum Beispiel so aussehen:
[STAThread]
static void Main()
{
   Application.ThreadException += new ThreadExceptionEventHandler(meineThreadExceptionBehandlung);
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);
   Application.Run(new Form1());
}
Was jetzt noch fehlt, ist die die entsprechende Methode, die bei einem Fehlerfall ausgeführt wird. Dort sollte man natürlich alle Maßnahmen treffen, um das Programm wieder in einen stabilen Zustand zu bringen. Im folgenden Beispiel hat der Benutzer nur die Wahl den Fehler zu ignorieren oder die Anwendung zu schließen.
static void meineThreadExceptionBehandlung(object sender, ThreadExceptionEventArgs e)
{
   DialogResult dia = MessageBox.Show(e.Exception.ToString(), "Ein Fehler ist aufgetreten (ThreadException)", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
   if(dia == DialogResult.Cancel)
   {
      Application.Exit();
   }
}
Obwohl das Ereignis im letzten Beispiel ThreadException heißt, ist es nicht geeignet die Fehlerbehandlung in Multithreading Szenarien zu übernehmen. Eine ThreadException fängt ausschließlich die Fehler des Hauptthreads. Will man auch mögliche Exception der anderen Threads abfangen, ist eine Erweiterung des Programmes nötig. Das Ereignis UnhandledException in der AppDomain der Anwendung ist hierfür geeignet.

Die erweiterte Main-Methode:
[STAThread]
static void Main()
{
   AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(meineUnhandledExceptionBehandlung);
   Application.ThreadException += new ThreadExceptionEventHandler(meineThreadExceptionBehandlung);
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);
   Application.Run(new Form1());
}
Und eine weitere Methode zur Fehlerbehandlung:
static void meineUnhandledExceptionBehandlung(object sender, UnhandledExceptionEventArgs e)
{
   MessageBox.Show(e.ExceptionObject.ToString(), "Ein Fehler ist aufgetreten (UnhandledException)", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Im Unterscheid zu einer ThreadException ist es hier nicht möglich den Fehler zu ignorieren. Die Anwendung wird sich immer beenden. So hat man nun letztmalig Gelegenheit kritische Daten zu speichern und einen einigermassen sanften Abbruch herbeizuführen.