Fortgeschrittene Fehlerbehandlung in C#: Vom einfachen try/catch zu industriellen Mustern
\n\nFehlerbehandlung ist nicht nur das Abfangen von Ausnahmen, sondern die Kunst, zuverlässige Systeme zu entwerfen. In C# (.NET) beschränken sich Entwickler oft auf das Konstrukt try/catch, aber die Plattform bietet weitaus leistungsfähigere Mechanismen. In diesem Artikel analysieren wir fortgeschrittene Techniken: von Ausnahmefiltern bis hin zu Wiederholungsmustern und der Übergabe von Fehlerkontexten.
1. Ausnahmefilter: Präzision statt Platzhalter
\n\nAusnahmefilter (Exception Filters), die mit C# 6.0 eingeführt wurden, ermöglichen es, eine Bedingung für den Eintritt in einen catch-Block anzugeben, ohne die Ausnahme erneut auszulösen. Dies ist wesentlich sauberer als der klassische Ansatz mit einem if innerhalb des Blocks.
try\n{\n // Code, der eine Ausnahme auslösen kann\n var result = ProcessData(input);\n}\ncatch (CustomException ex) when (ex.ErrorCode == 1001)\n{\n // Verarbeite nur Fehler mit Code 1001\n Log.Error($"Kritischer Fehler: {ex.Message}");\n throw; // Bei Bedarf weiterleiten\n}\ncatch (CustomException ex) when (ex.IsRecoverable)\n{\n // Behebbarer Fehler – protokollieren und fortfahren\n Log.Warning($"Behebbarer Fehler: {ex.Message}");\n RetryOperation();\n}\n\nVorteile:
\n- \n
- Kein erneutes
throwzur Filterung erforderlich. \n - Verbesserte Lesbarkeit und Leistung (der Aufrufstapel wird nicht beschädigt). \n
- Mehrere
catch-Blöcke mit unterschiedlichen Bedingungen können kombiniert werden. \n
2. Das "Wiederholungs"-Muster (Retry) und Polly
\n\nNicht alle Fehler sind fatal. Für vorübergehende Ausfälle (Netzwerkprobleme, Datenbanksperren) verwenden Sie das Muster der Wiederholungsversuche. Die integrierten Mittel von C# sind begrenzt, daher wird die Bibliothek Polly empfohlen.
\n\nusing Polly;\nusing Polly.Retry;\n\n// Definieren der Wiederholungsrichtlinie\nAsyncRetryPolicy retryPolicy = Policy\n .Handle<HttpRequestException>() // Nur HTTP-Fehler behandeln\n .WaitAndRetryAsync(\n 3, // Anzahl der Versuche\n retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // Exponentielle Verzögerung\n onRetry: (exception, timeSpan, retryCount, context) =>\n {\n Log.Warning($"Versuch {retryCount} fehlgeschlagen. Wiederholung in {timeSpan.Seconds} Sek.");\n });\n\n// Anwenden der Richtlinie\nawait retryPolicy.ExecuteAsync(async () =>\n{\n var response = await httpClient.GetAsync("https://api.example.com/data");\n response.EnsureSuccessStatusCode();\n});\n\nWichtig: Verwenden Sie keine unendlichen Wiederholungen. Geben Sie immer eine maximale Anzahl von Versuchen an und nutzen Sie Circuit Breaker, um das System vor kaskadierenden Ausfällen zu schützen.
\n\n3. Korrekte Weitergabe von Ausnahmen: throw vs throw ex
\n\nEiner der häufigsten Fehler ist der Verlust des Aufrufstapels. Der Unterschied zwischen throw und throw ex ist für das Debugging entscheidend.
try\n{\n // ...\n}\ncatch (Exception ex)\n{\n // FALSCH: Verliert den ursprünglichen Stack\n // throw ex; \n\n // RICHTIG: Behält den Stack\n throw;\n\n // ODER: Kontext hinzufügen, ohne das Original zu verlieren\n // throw new ApplicationException("Fehler in Modul X", ex);\n}\n\nWenn Sie Kontext hinzufügen müssen, verwenden Sie ExceptionDispatchInfo (aus dem Namespace System.Runtime.ExceptionServices) für die präzise Beibehaltung des Stacks beim Weiterwerfen zwischen Threads.
ExceptionDispatchInfo.Capture(exception).Throw(); // Behält den ursprünglichen Stack\n\n4. Benutzerdefinierte Ausnahmen und Fehlerkontext
\n\nStandardausnahmen enthalten oft keinen Geschäftskontext. Erstellen Sie Ihre eigenen Ausnahmeklassen mit zusätzlichen Eigenschaften.
\n\npublic class PaymentProcessingException : Exception\n{\n public int OrderId { get; }\n public dec