C#-Ausnahmebehandlung
Die Behandlung von Ausnahmen ist ein entscheidender Aspekt der robusten Anwendungsentwicklung. Tim Coreys Video zu "Handling Exceptions in C# - When to catch them, where to catch them, and how to catch them" erklärt ausführlich, was Exceptions sind, wie und wo man sie behandelt.
Dieser Artikel soll die Ausnahmebehandlung in C# anhand des Videos von Tim Corey erklären. Es ist eine leistungsstarke Funktion, die es Entwicklern ermöglicht, Fehler und außergewöhnliche Bedingungen zu verwalten, die während der Programmausführung auftreten. Mit den try-, catch- und finally-Blöcken bietet C# eine strukturierte Möglichkeit, Laufzeitfehler zu behandeln, Ausnahmen zu protokollieren und den Programmfluss aufrechtzuerhalten.
Einführung
Tim beginnt mit der Erklärung, dass viele Entwickler eine falsche Vorstellung von Ausnahmen und deren Behandlung haben. Er betont, wie wichtig es ist, zu verstehen, was Ausnahmen sind und wo und wie man sie richtig behandelt, um robustere Anwendungen zu erstellen.
Erstellung einer Demo-Konsolenanwendung
Tim erstellt eine Konsolenanwendung in Visual Studio 2017, um die Behandlung von Ausnahmen zu demonstrieren. Er empfiehlt die Verwendung von Konsolenanwendungen für das Testen neuer Themen, da sie nur minimale Einstellungen erfordern und einfach zu handhaben sind.
using System;
namespace ExceptionsDemo
{
class Program
{
static void Main(string[] args)
{
// Placeholder for input and output operations
Console.ReadLine();
}
}
}
using System;
namespace ExceptionsDemo
{
class Program
{
static void Main(string[] args)
{
// Placeholder for input and output operations
Console.ReadLine();
}
}
}
Erstellen einer Klassenbibliothek
Tim fügt der Lösung eine Klassenbibliothek hinzu, um ein reales Szenario zu simulieren, in dem sich verschiedene Methoden gegenseitig aufrufen.

Er löscht die Standardklasse und erstellt eine neue Klasse namens DemoCode.
public class DemoCode
{
// Method to retrieve a number based on the provided position
public int GetNumber(int position)
{
int[] numbers = { 1, 4, 7, 2 };
return numbers[position];
}
// Intermediate method calls GetNumber
public int ParentMethod(int position)
{
return GetNumber(position);
}
// Top-level method calls ParentMethod
public int GrandparentMethod(int position)
{
return ParentMethod(position);
}
}
public class DemoCode
{
// Method to retrieve a number based on the provided position
public int GetNumber(int position)
{
int[] numbers = { 1, 4, 7, 2 };
return numbers[position];
}
// Intermediate method calls GetNumber
public int ParentMethod(int position)
{
return GetNumber(position);
}
// Top-level method calls ParentMethod
public int GrandparentMethod(int position)
{
return ParentMethod(position);
}
}
Die Klasse DemoCode enthält Methoden, die sich gegenseitig aufrufen und schließlich eine Zahl aus einem Array auf der Grundlage der angegebenen Position abrufen.
Eine Ausnahme simulieren
Tim erklärt, dass die Anwendung eher Misserfolge als Erfolge aufzeigen soll. Er führt eine Out-of-Bounds-Exception ein, indem er eine ungültige Position an die GrandparentMethod übergibt.
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
Die Ausführung des obigen Codes mit einer ungültigen Position führt zu einer IndexOutOfRangeException. Tim zeigt, wie der Visual Studio Debugger das Problem hervorhebt und detaillierte Informationen über die Ausnahme liefert.
Wie man try-catch NICHT verwendet
Tim erklärt einen häufigen Fehler, den Entwickler machen, wenn sie zum ersten Mal etwas über try-catch-Blöcke lernen. Sie umschließen oft den gesamten Codeblock, in dem sie eine Ausnahme erwarten, was zu einer unsachgemäßen Behandlung führen kann.
try
{
int output = 0;
output = numbers[position];
return output;
}
catch (Exception ex)
{
// Avoid returning default values that can mask the problem
return 0;
}
try
{
int output = 0;
output = numbers[position];
return output;
}
catch (Exception ex)
{
// Avoid returning default values that can mask the problem
return 0;
}
Tim betont, dass dieser Ansatz problematisch ist, weil er die Ausnahme versteckt und die Ausführung mit falschen Annahmen fortsetzt. Beispielsweise ist die Angabe von 0 als Standardwert nicht angemessen und kann zu weiteren Problemen führen.
Korrekte Ausnahmebehandlung
Tim betont, dass Ausnahmen wichtige Informationen über unerwartete Zustände in der Anwendung liefern. Wenn die Anwendung in einem solchen Zustand weiterläuft, kann dies zu weiteren Fehlern und Datenbeschädigungen führen.
Anstatt Ausnahmen zu schlucken, ist es wichtig, sie angemessen zu behandeln. Hier ist ein besserer Ansatz:
try
{
return numbers[position];
}
catch (Exception ex)
{
// Log the exception or handle it appropriately
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to be handled by a higher-level handler
}
try
{
return numbers[position];
}
catch (Exception ex)
{
// Log the exception or handle it appropriately
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to be handled by a higher-level handler
}
Indem Sie die Ausnahme erneut auslösen, stellen Sie sicher, dass das Problem weitergegeben wird und bei Bedarf auf einer höheren Ebene behandelt werden kann.
Nützliche Informationen für den Benutzer
Tim erklärt, dass einige Ausnahmen zwar elegant behandelt werden können, ohne die Anwendung zum Absturz zu bringen, es aber wichtig ist, dem Benutzer nützliches Feedback zu geben. Zum Beispiel die Anzeige eines Nachrichtenfeldes oder einer Benachrichtigung mit der Option, den Vorgang zu wiederholen.
Weitere nützliche Informationen: StackTrace
Tim demonstriert, wie man die StackTrace-Eigenschaft des Ausnahmeobjekts verwendet, um detaillierte Informationen darüber zu erhalten, wo die Ausnahme aufgetreten ist. Dazu gehören die Klassen-, Methoden- und Zeilennummern, die für die Fehlersuche von unschätzbarem Wert sind.
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
throw;
}
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
throw;
}
Die StackTrace-Eigenschaft liefert eine vollständige Aufzeichnung des Aufrufstapels und hilft den Entwicklern, den genauen Ort des Problems zu bestimmen.
Proper Platzierung von try-catch
Tim erklärt, dass es beim richtigen Umgang mit Ausnahmen nicht nur darum geht, sie abzufangen, sondern auch darum, zu wissen, wo man seine try-catch-Blöcke platziert. Der Schlüssel liegt darin, try-catch-Blöcke auf einer Ebene zu platzieren, auf der Sie genügend Kontext haben, um die Ausnahme angemessen zu behandeln.
Beispiel für unsachgemäße Platzierung
Wenn Sie einen Try-Catch-Block tief im Aufrufstapel platzieren, können Sie die Ausnahme oft nicht effektiv behandeln, weil Ihnen der Kontext der übergeordneten Operationen fehlt.
// Deep level exception handling (not ideal)
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
// Deep level exception handling (not ideal)
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
Beispiel für die korrekte Platzierung
Durch die Platzierung des try-catch-Blocks auf der obersten Ebene, z. B. in der Benutzeroberfläche oder am Einstiegspunkt der Anwendung, können Sie Ausnahmen mit dem vollständigen Kontext der Operation behandeln.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Auf diese Weise können Sie dem Benutzer informativere Meldungen geben und entscheiden, ob die Anwendung weiterlaufen kann oder ob sie beendet werden sollte.
Stack-Trace-Informationen
Tim unterstreicht die Bedeutung von Stack-Trace-Informationen bei der Diagnose von Ausnahmen. Die Stapelverfolgung bietet einen detaillierten Aufrufverlauf, der zeigt, wo die Ausnahme aufgetreten ist und welche Kette von Methodenaufrufen zu ihr geführt hat.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Diese Ausgabe gibt die genaue Position der Ausnahme und den Pfad durch den Code an, was die Fehlersuche und -behebung erleichtert.
Demonstration der Handhabungslogik
Tim zeigt, wie man mit Logik auf der entsprechenden Ebene umgeht. Wenn eine Methode beispielsweise für das Öffnen und Schließen einer Datenbankverbindung zuständig ist, sollte sie Ausnahmen behandeln, um sicherzustellen, dass die Ressourcen richtig verwaltet werden.
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
Console.WriteLine("Close database connection");
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Ensure the exception is propagated
}
}
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
Console.WriteLine("Close database connection");
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Ensure the exception is propagated
}
}
Wenn in diesem Beispiel eine Ausnahme auftritt, wird die Datenbankverbindung nicht ordnungsgemäß geschlossen, was zu möglichen Ressourcenverlusten führt. Durch Hinzufügen eines try-catch-Blocks können Sie sicherstellen, dass die Verbindung auch dann geschlossen wird, wenn eine Ausnahme auftritt.
Verwendung des finally Block
Tim stellt den finally-Block vor, der sicherstellt, dass ein bestimmter Code unabhängig davon ausgeführt wird, ob eine Ausnahme auftritt. Dies ist besonders nützlich für die Bereinigung von Ressourcen, wie z. B. das Schließen von Datenbankverbindungen.
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to ensure it's handled by a higher-level handler
}
finally
{
Console.WriteLine("Close database connection");
}
}
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to ensure it's handled by a higher-level handler
}
finally
{
Console.WriteLine("Close database connection");
}
}
Der finally-Block wird nach den try- und catch-Blöcken ausgeführt, um sicherzustellen, dass die Verbindung geschlossen wird, auch wenn eine Ausnahme ausgelöst wird.
Die Wurfaussage
Tim erklärt, wie wichtig es ist, Ausnahmen erneut auszulösen, um sie im Call Stack weiterzuleiten. Auf diese Weise können übergeordnete Handler die Ausnahmen angemessen verarbeiten.
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throws the exception to be handled by the calling method
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throws the exception to be handled by the calling method
}
Wiederholung der Ausnahme mit throw; die Übersetzung stellt sicher, dass der vollständige Stack-Trace erhalten bleibt, der einen wertvollen Kontext für die Fehlersuche bietet.
Ausnahmen richtig aufbauschen
Tim demonstriert, wie sich Ausnahmen durch den Aufrufstapel nach oben schieben. Jede Methode prüft auf einen Try-Catch-Block und behandelt die Ausnahme entweder selbst oder übergibt sie an den Aufrufer.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
In diesem Beispiel fängt die GrandparentMethod die Ausnahme ab, protokolliert sie und löst sie erneut aus. Der try-catch-Block auf oberster Ebene in der Konsolenanwendung behandelt dann die Ausnahme und zeigt die Fehlermeldung und den Stack-Trace an.
Gängige Fehler bei der Behandlung von Ausnahmen
Tim hebt mehrere häufige Fehler hervor, die Entwickler bei der Behandlung von Ausnahmen machen:
-
Verwendung throw ex;:
-
Das Umschreiben des Stacktrace und der Verlust von wertvollem Kontext.
- Beispiel:
catch (Exception ex) { // Incorrect throw ex; // Rewrites stack trace }catch (Exception ex) { // Incorrect throw ex; // Rewrites stack trace }
-
-
Werfen einer neuen Ausnahme:
-
Erstellen einer neuen Ausnahme mit einer benutzerdefinierten Meldung, wobei die ursprüngliche Stack-Trace verloren geht.
- Beispiel:
catch (Exception ex) { // Incorrect throw new Exception("I blew up"); }catch (Exception ex) { // Incorrect throw new Exception("I blew up"); }
-
Erstellen einer neuen Exception ohne Verlust des ursprünglichen Stack Trace
Tim erklärt, wie man eine neue Ausnahme erstellt und dabei den ursprünglichen Stack-Trace beibehält. Dies kann nützlich sein, wenn Sie eine aussagekräftigere Fehlermeldung oder einen anderen Ausnahmetyp bereitstellen möchten, ohne dabei den Kontext des ursprünglichen Fehlers zu vernachlässigen.
catch (Exception ex)
{
throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
throw new ArgumentException("You passed in bad data", ex);
}
Durch die Übergabe der ursprünglichen Ausnahme (ex) als innere Ausnahme bleibt der ursprüngliche Stack-Trace erhalten, der für die Fehlersuche entscheidend ist.
Bewahren von Stack-Trace-Informationen
Tim demonstriert, wie man beim Erstellen einer neuen Ausnahme auf die ursprüngliche Ausnahmemeldung und den Stack-Trace zugreift.
catch (Exception ex)
{
Console.WriteLine("You passed in bad data");
Console.WriteLine(ex.StackTrace);
throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
Console.WriteLine("You passed in bad data");
Console.WriteLine(ex.StackTrace);
throw new ArgumentException("You passed in bad data", ex);
}
Dadurch wird sichergestellt, dass die Ausnahme, die den Stack hinaufgeworfen wird, sowohl die neue Nachricht als auch die ursprünglichen Ausnahmedetails enthält.
Schleifen durch innere Ausnahmen
Tim bietet eine Methode zum Durchlaufen aller inneren Ausnahmen, um deren Meldungen und Stack Traces zu extrahieren.
catch (Exception ex)
{
Exception inner = ex;
while (inner != null)
{
Console.WriteLine(inner.StackTrace);
inner = inner.InnerException;
}
throw;
}
catch (Exception ex)
{
Exception inner = ex;
while (inner != null)
{
Console.WriteLine(inner.StackTrace);
inner = inner.InnerException;
}
throw;
}
Diese Schleife durchläuft jede innere Ausnahme und gibt deren Stack-Trace aus, um sicherzustellen, dass alle Ebenen von Ausnahmen berücksichtigt werden.
Unterschiedliche Ausnahmen unterschiedlich behandeln
Tim erörtert, wie man verschiedene Arten von Ausnahmen mit mehreren Catch-Blöcken behandelt. Dies ermöglicht eine spezifische Behandlung je nach Ausnahmetyp.
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
In diesem Beispiel wird ArgumentException speziell behandelt, indem eine benutzerdefinierte Meldung ausgegeben wird, während alle anderen Ausnahmen auf einen allgemeinen Handler zurückgreifen, der die Ausnahmemeldung und den Stack-Trace ausgibt.

Bedeutung der Reihenfolge in mehreren Catch-Blöcken
Tim betont, wie wichtig die Reihenfolge bei der Verwendung mehrerer Catch-Blöcke ist. Die spezifischsten Ausnahmen sollten zuerst abgefangen werden, gefolgt von allgemeineren Ausnahmen.
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Wenn ein allgemeinerer Catch-Block vor einem spezifischen Block erscheint, fängt er alle Ausnahmen ab, und der spezifische Catch-Block wird nie erreicht, was zu Kompilierungsfehlern führt.
Abschluss
Tim Coreys fortgeschrittener Video-Leitfaden zur Ausnahmebehandlung in C# deckt wichtige Techniken für die Erstellung neuer Ausnahmen, die Beibehaltung von Stack Traces und die effektive Verwendung mehrerer Catch-Blöcke ab. Indem sie seine Best Practices befolgen, können Entwickler robuste Anwendungen erstellen, die Ausnahmen zuverlässig behandeln und wertvolle Debugging-Informationen liefern.
