November 2015
Band 30, Nummer 12
.NET-Grundlagen – C#-Ausnahmebehandlung
Von Mark Michaelis | November 2015
Willkommen bei der Auftaktausgabe der .NET-Grundlagen. An dieser Stelle können Sie alles verfolgen, was sich um Microsoft .NET Framework herum ereignet, seien es Fortschritte in der nächsten C#-Version (aktuell C# 7.0), verbesserte interne Funktionen in .NET oder Ereignisse an der Roslyn- und .NET Core-Front (wie etwa die Verlagerung von MSBuild nach Open Source).
Ich habe mit .NET Code erstellt und entwickelt, seit die Vorschau im Jahr 2000 vorgestellt wurde. Vieles, über das ich schreiben werde, wird sich auch nicht auf die neuen Teile beziehen, sondern um das Nutzen der Technologie im Sinne bewährter Methoden drehen.
Ich lebe in Spokane, Washington, wo ich der „Obernerd“ einer High-End-Consultingfirma namens IntelliTect (IntelliTect.com) bin. Die Spezialität von IntelliTect ist das Knacken von „harten Nüssen“ in der Entwicklung. Ich bin seit 20 Jahren Microsoft MVP (aktuell für C#), acht Jahre davon als Microsoft-Regionalleiter. Heute beginnt diese Kolumne mit einem Blick auf die aktualisierten Richtlinien zur Ausnahmebehandlung.
Seit C# 6.0 gibt es zwei neue Features für die Ausnahmebehandlung. Erstens wurde Unterstützung für Ausnahmebedingungen aufgenommen – mit der Möglichkeit, einen Ausdruck anzugeben, mit dem eine Ausnahme vor dem Eintritt in einen Catch-Block ausgefiltert werden kann, bevor der ganze Stapel abgewickelt wird. Zweitens wurde Async-Unterstützung innerhalb eines Catch-Blocks hinzugefügt, was in C# 5.0 nicht möglich war, als Async-Unterstützung in die Sprache eingeführt wurde. Außerdem gab es viele weitere Änderungen, die in den letzten fünf Versionen von C# und dem entsprechenden .NET Framework erfolgt sind und die in einigen Fällen so tiefgreifend sind, dass eine Überarbeitung der Richtlinien zur Codeerstellung in C# erforderlich wurde. In dieser Folge gehe ich eine Reihe dieser Änderungen durch und stelle aktualisierte Richtlinien für die Codeerstellung im Hinblick auf die Ausnahmebehandlung vor – das Abfangen von Ausnahmen.
Abfangen von Ausnahmen: Übersicht
Wie allgemein bekannt ist, ermöglicht das Auslösen eines bestimmten Ausnahmetyps dem Fänger, den Typ der Ausnahme selbst zum Identifizieren des Problems zu verwenden. Anders gesagt, es ist nicht erforderlich, die Ausnahme abzufangen und eine switch-Anweisung in der Ausnahmenachricht zu verwenden, um zu bestimmen, welche Aktion hinsichtlich der Ausnahme ergriffen werden soll. Stattdessen lässt C# mehrere Catch-Blöcke zu, von denen jeder auf einen bestimmten Ausnahmetyp spezialisiert ist, wie in Abbildung 1 gezeigt.
Abbildung 1 Abfangen verschiedener Ausnahmetypen
using System;
public sealed class Program
{
public static void Main(string[] args)
try
{
// ...
throw new InvalidOperationException(
"Arbitrary exception");
// ...
}
catch(System.Web.HttpException exception)
when(exception.GetHttpCode() == 400)
{
// Handle System.Web.HttpException where
// exception.GetHttpCode() is 400.
}
catch (InvalidOperationException exception)
{
bool exceptionHandled=false;
// Handle InvalidOperationException
// ...
if(!exceptionHandled)
// In C# 6.0, replace this with an exception condition
{
throw;
}
}
finally
{
// Handle any cleanup code here as it runs
// regardless of whether there is an exception
}
}
}
Wenn eine Ausnahme auftritt, springt die Ausnahme zum ersten Catch-Block, der sie verarbeiten kann. Wenn dem Try-Block mehr als ein Catch-Block zugeordnet ist, wird die Nähe der Übereinstimmung durch die Vererbungskette (unter der Annahme, das keine C# 6.0-Ausnahmebedingung vorliegt) bestimmt, und der erste passende Block verarbeitet die Ausnahme. Beispielsweise ist für die ausgelöste Ausnahme, obwohl sie den Typ „System.Exception“ aufweist, diese „ist eine“-Beziehung in der Vererbung begründet, da „System.InvalidOperationException“ letztlich aus „System.Exception“ abgeleitet ist. Da „InvalidOperationException“ die beste Übereinstimmung mit der ausgelösten Ausnahme aufweist, fängt „catch(InvalidOperationException...)“ die Ausnahme ab, nicht ein eventuell vorhandener „catch(Exception...)“-Block.
Catch-Blöcke müssen in der Reihenfolge vom spezifischsten zum allgemeinsten auftreten, um einen Kompilierzeitfehler zu vermeiden (wieder unter der Annahme, dass keine C# 6.0-Ausnahmebedingung vorliegt). Beispielsweise bewirkt das Hinzufügen eines Blocks „catch(Exception...)“ vor allen anderen Ausnahmen einen Kompilierzeitfehler, da alle vorhergehenden Ausnahmen an irgendeinem Punkt ihrer Vererbungskette von „System.Exception“ abgeleitet sind. Beachten Sie ferner, dass für den Catch-Block kein benannter Parameter erforderlich ist. Tatsächlich ist sogar ein abschließender Catch-Block ohne Angabe des Parametertyps zulässig, leider, wie unter „Allgemeiner Catch-Block“ erörtert.
Gelegentlich stellt sich nach dem Abfangen einer Ausnahme heraus, dass eine ordnungsgemäße Verarbeitung der Ausnahme nicht möglich ist. In diesem Szenario bestehen zwei Optionen. Die erste Option besteht darin, erneut eine Ausnahme auszulösen, eine andere. Es gibt drei häufige Szenarien, in denen das sinnvoll sein kann:
Szenario Nr. 1 Die abgefangene Ausnahme bezeichnet das auslösende Problem nicht ausreichend. Wenn z. B. „System.Net.WebClient.DownloadString“ mit einer gültigen URL aufgerufen wird, löst die Runtime möglicherweise eine „System.Net.WebException“ aus, wenn keine Netzwerkverbindung besteht – die gleiche Ausnahme, die bei einer nicht vorhandenen URL ausgelöst wird.
Szenario Nr. 2 Die abgefangene Ausnahme beinhaltet private Daten, die weiter oben in der Aufrufkette nicht verfügbar gemacht werden dürfen. Beispielsweise enthielt eine sehr frühe Version von CLR v1 (noch vor der Alphaversion) eine Ausnahme, die eine Nachricht in dieser Art enthielt „Sicherheitsausnahme: Sie haben keine Berechtigung, den Pfad von ‚c:\temp\foo.txt‘ zu bestimmen“.
Szenario Nr. 3 Der Ausnahmetyp ist zu spezifisch, um vom Aufrufer verarbeitet zu werden. Beispielsweise tritt eine „System.IO“-Ausnahme (wie etwa „UnauthorizedAccessException IOException FileNotFoundException DirectoryNotFoundException PathTooLongException“, „NotSupportedException“ oder „SecurityException ArgumentException“) auf dem Server auf, während ein Webdienst aufgerufen wird, um eine Postleitzahl nachzuschlagen.
Achten Sie beim erneuten Auslösen einer anderen Ausnahme darauf, dass die ursprüngliche Ausnahme verloren gehen könnte (im Fall von Szenario 2 vermutlich mit Absicht). Um das zu verhindern, legen Sie die InnerException-Eigenschaft der umschließenden Ausnahme auf die abgefangene Ausnahme fest – im Allgemeinen kann diese Zuweisung über den Konstruktor erfolgen –, es sei denn, dadurch würden private Daten zugänglich gemacht, die weiter oben in der Aufrufkette nicht offengelegt werden sollen. Auf diese Weise ist die ursprüngliche Stapelüberwachung nach wie vor verfügbar.
Wenn Sie die innere Ausnahme nicht festlegen und trotzdem die Ausnahmeinstanz nach der Auslöseanweisung („throw exception“) angeben, wird der Ort in der Stapelüberwachung auf die Ausnahmeinstanz festgelegt. Dieses Zurücksetzen findet auch dann statt, wenn Sie die zuvor bereits abgefangene Ausnahme erneut auslösen, deren Stapelüberwachung bereits festgelegt ist.
Eine zweite Option beim Abrufen einer Ausnahme besteht in der Feststellung, dass die Ausnahme nicht sinnvoll verarbeitet werden kann. In diesem Szenario ist es sinnvoll, die genau gleiche Ausnahme noch einmal auszulösen – sie an den nächsten Handler in der Aufrufkette zu senden. Dies ist im Catch-Block „InvalidOperationException“ von Abbildung 1 dargestellt. Eine Throw-Anweisung tritt ohne eine Angabe der auszulösenden Ausnahme auf („throw“ ist auf sich gestellt), obwohl eine Ausnahmeinstanz („exception“) im Bereich des Catch-Blocks vorkommt, die erneut ausgelöst werden könnte. Beim Auslösen einer bestimmten Ausnahme würden sämtliche Informationen in der Aufrufliste mit der neuen Auslöseposition aktualisiert. Das hätte zur Folge, dass alle Listeninformationen, die auf die Position verweisen, an der die Ausnahme ursprünglich auftrat, verloren gehen würden, wodurch die Diagnose des Problems erheblich erschwert würde. Bei der Feststellung, dass ein Catch-Block eine Ausnahme nicht ordnungsgemäß verarbeiten kann, sollte die Ausnahme mithilfe einer leeren throw-Anweisung wieder ausgelöst werden.
Unabhängig davon, ob Sie die gleiche Ausnahme erneut auslösen oder sie umschließen, die allgemeine Regel läuft darauf hinaus, dass das Melden oder Protokollieren weiter unten in der Aufrufliste vermieden wird. In anderen Worten, protokollieren Sie eine Ausnahme nicht jedes Mal, wenn Sie sie abfangen und erneut auslösen. Das bringt unnötigen Clutter in den Protokolldateien mit sich, ohne zu neuen Einsichten zu führen, weil jedes Mal die gleichen Informationen erfasst werden. Außerdem enthält die Ausnahme die Daten der Stapelüberwachung zum Zeitpunkt ihrer Auslösung, das brauchen Sie also nicht immer wieder festzuhalten. Protokollieren Sie die Ausnahme unter allen Umständen, wenn sie verarbeitet wird oder, falls sie nicht verarbeitet wird, protokollieren Sie die Ausnahme, um sie aufzuzeichnen, bevor Sie einen Prozess beenden.
Auslösen vorhandener Ausnahmen ohne Ersetzung der Stapelinformationen
In C# 5.0 wurde ein Mechanismus eingeführt, der das Auslösen einer bereits zuvor ausgelösten Ausnahme ohne Verlust der Stapelüberwachungsinformationen der ursprünglichen Ausnahme ermöglicht. Dadurch können Sie Ausnahmen beispielsweise sogar außerhalb eines Catch-Blocks erneut auslösen, also ohne eine leere Throw-Anweisung zu verwenden. Das ist zwar nur ziemlich selten erforderlich, bei manchen Gelegenheiten werden Ausnahmen aber umschlossen oder gespeichert, bis die Programmausführung den Catch-Block verlassen hat. Beispielsweise kann Code, der mit mehreren Threads arbeitet, eine Ausnahme mit einer „AggregateException“ umschließen. .NET Framework 4.5 stellt eigens eine Klasse „System.Runtime.ExceptionServices.ExceptionDispatchInfo“ zur Verfügung, um dieses Szenario mithilfe ihrer statischen Methode „Capture“ und der Instanzenmethode „Throw“ zu behandeln. Abbildung 2 zeigt das erneute Auslösen der Ausnahme ohne Zurücksetzen der Informationen der Stapelüberwachung oder der Verwendung einer leeren throw-Anweisung.
Abbildung 2 Verwenden von „ExceptionDispatchInfo“ zum erneuten Auslösen einer Ausnahme
using System
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
Task task = WriteWebRequestSizeAsync(url);
try
{
while (!task.Wait(100))
{
Console.Write(".");
}
}
catch(AggregateException exception)
{
exception = exception.Flatten();
ExceptionDispatchInfo.Capture(
exception.InnerException).Throw();
}
Die Methode „ExeptionDispatchInfo.Throw“ wird hier vom Compiler nicht in der Weise als return-Anweisung angesehen, wie das bei einer normalen throw-Anweisung geschehen kann. Wenn z. B. die Methodensignatur einen Wert zurückgegeben hat, von dem Codepfad mit „ ExceptionDispatchInfo.Throw“ aber kein Wert zurückgegeben wurde, würde der Compiler einen Fehler ausgeben, der angibt, dass kein Wert zurückgegeben wurde. Gelegentlich können Entwickler gezwungen sein, eine return-Anweisung auf „ExceptionDispatchInfo.Throw“ folgen zu lassen, obwohl eine solche Anweisung zur Laufzeit nie ausgeführt würde – stattdessen würde die Ausnahme ausgelöst.
Abfangen von Ausnahmen in C# 6.0
Die allgemeine Richtlinie bei der Ausnahmebehandlung besteht darin, das Abfangen von Ausnahmen zu vermeiden, die Sie nicht im ganzen Umfang bewältigen können. Da Catch-Ausdrücke vor C# 6.0 aber nur nach dem Ausnahmetyp filtern konnten, erforderte die Möglichkeit, die Ausnahmedaten und den Kontext vor dem Abwickeln des Stapels zu untersuchen, dass der Catch-Block zum Handler der Ausnahme wurde, bevor er sie untersuchen konnte. Unglücklicherweise, wenn festgestellt wird, dass die Ausnahme nicht verarbeitet werden soll, ist es mühselig Code zu erstellen, der einem anderen Catch-Block im gleichen Kontext die Verarbeitung der Ausnahme ermöglicht. Und das erneute Auslösen der gleichen Ausnahme führt dazu, dass der Ausnahmeprozess mit seinen zwei Durchgängen erneut aufgerufen werden muss, ein Prozess, der zuerst die Übermittlung der Ausnahme entlang der Aufrufkette nach oben mit sich bringt, bis sie einen Block findet, der sie behandelt, und zweitens die Aufrufliste für jeden Rahmen zwischen der Ausnahme und der Position des Catches abzuwickeln.
Sobald eine Ausnahme ausgelöst ist, wäre es, statt die Aufrufliste im Catch-Block abzuwickeln, nur um die Ausnahme dann erneut auszulösen, weil ihre genauere Untersuchung ergibt, dass sie nicht ausreichend behandelt werden kann, offenbar sinnvoller, die Ausnahme erst gar nicht abzufangen. Seit C# 6.0 ist ein zusätzlicher Bedingungsausdruck für Catch-Blöcke verfügbar. Statt nur auf der Grundlage des übereinstimmen Ausnahmetyps einzuschränken, ob ein Catch-Block passt, bietet C# 6.0 Unterstützung für eine Bedingungsklausel. Die when-Klausel ermöglicht die Angabe eines Booleschen Ausdrucks, der die Filterung des Catch-Blocks weiter einschränkt, um die Ausnahme nur dann zu behandeln, wenn er Ausdruck wahr ist. Der Block „System.Web.HttpException“ in Abbildung 1 bildet dafür ein Beispiel mit seinem Gleichheitsvergleichsoperator.
Eine interessante Folge des Ausnahmeausdrucks besteht darin, dass der Compiler bei Angabe einer Ausnahmebedingung das Auftreten von Catch-Blöcken in der Reihenfolge der Vererbungskette nicht erzwingt. Beispielsweise kann ein Catch vom Typ „System.ArgumentException“ mit einer begleitenden Ausnahmebedingung jetzt vor dem spezifischeren Typ „System.ArgumentNullException“ auftreten, obwohl der letztere aus dem ersteren abgeleitet ist. Dies ist wichtig, da es Ihnen die Möglichkeit gibt, eine spezifische Ausnahmebedingung zu erstellen, die mit einem allgemeinen Ausnahmetyp gekoppelt ist, dem ein spezifischerer Ausnahmetyp (mit oder ohne Ausnahmebedingung) folgt. Das Laufzeitverhalten bleibt mit früheren Versionen von C# konsistent; Ausnahmen werden vom ersten passenden Catch-Block abgefangen. Die neu hinzugekommene Komplexität besteht lediglich darin, dass die Frage, ob ein Catch-Block passt oder nicht, anhand der Kombination aus dem Typ und der Ausnahmebedingung bestimmt wird, und der Compiler erzwingt die Reihenfolge nur relativ zu Catch-Blöcken ohne Ausnahmebedingungen. Beispielsweise kann „catch(System.Exception)“ mit einer Ausnahmebedingung vor „catch(System.ArgumentException)“ mit oder ohne Ausnahmebedingung auftreten. Sobald jedoch ein Catch für einen Ausnahmetyp ohne Ausnahmebedingung auftritt, kann kein Catch eines spezifischeren Ausnahmeblocks (wie etwa „catch(System.ArgumentNullException)“) mehr auftreten, gleich, ob er eine Ausnahmebedingung aufweist oder nicht. Dies gibt dem Programmierer die „Flexibilität“, Ausnahmebedingungen zu erstellen, die potenziell ungeordnet sind – bei denen frühere Ausnahmebedingungen Ausnahmen abfangen, die für spätere vorgesehen sind, was die späteren potenziell sogar unabsichtlich unerreichbar machen kann. Letztlich ist die Reihenfolge Ihrer Catch-Blöcke ähnlich der Reihenfolge von if-else-Anweisungen. Sobald die Bedingung erfüllt ist, werden alle anderen Catch-Blöcke ignoriert. Anders als die Bedingungen in einer if-else-Anweisung müssen aber alle Catch-Blöcke die Ausnahmetypprüfung beinhalten.
Aktualisierte Richtlinien zur Ausnahmebehandlung
Das Beispiel mit dem Vergleichsoperator in Abbildung 1 ist trivial, die Ausnahmebedingung erschöpft sich aber nicht in solcher Einfachheit. Beispielsweise können Sie einen Methodenaufruf vornehmen, um eine Bedingung zu überprüfen. Die einzige Anforderung besteht darin, dass es sich bei dem Ausdruck um ein Prädikat handelt – er gibt einen Booleschen Wert zurück. Anders gesagt, Sie können prinzipiell beliebigen Code aus der Ausnahmeaufrufkette von Catches heraus ausführen. Dies eröffnet die Möglichkeit, nie mehr die gleiche Ausnahme abfangen und erneut auslösen zu müssen; im Prinzip sind Sie imstande, den Kontext hinreichend stark einzuschränken, bevor Sie die Ausnahme abfangen, um nur dann abzufangen, wenn das auch gültig ist. Dadurch wird die Richtlinie, das Abfangen von Ausnahmen zu vermeiden, die Sie nicht vollständig verarbeiten können, ganz und gar zur Realität. Tatsächlich kann jede Bedingungsprüfung, die eine leere throw-Anweisung umschließt, mit hoher Wahrscheinlichkeit mit einem Code-Smell geflaggt und vermieden werden. Erwägen Sie, einen Ausnahmeausdruck hinzuzufügen, statt eine leere throw-Anweisung verwenden zu müssen, es sei denn, um einen flüchtigen Status permanent zu machen, bevor ein Prozess beendet wird.
Mit diesen Gedanken im Hinterkopf sollten Entwickler die Verwendung von Bedingungsklauseln auf Kontextprüfungen beschränken. Das ist wichtig, denn wenn der Bedingungsausdruck seinerseits eine Ausnahme auslöst, wird diese neue Ausnahme ignoriert und die Bedingung als falsch behandelt. Daher sollten Sie das Auslösen von Ausnahmen im Bedingungsausdruck von Ausnahmen vermeiden.
Allgemeiner Catch-Block
In C# ist es erforderlich, dass jedes Objekt, das von Code ausgelöst wird, von „System.Exception“ abgeleitet sein muss. Diese Anforderung besteht jedoch nicht durchgängig in allen Sprachen. C/C++ lässt beispielsweise die Auslösung beliebiger Objekttypen zu, einschließlich verwalteter Ausnahmen, die nicht aus „System.Exception“ abgeleitet sind, sogar primitiver Typen, wie „int“ oder „string“. Seit C# 2.0 werden alle Ausnahmen, ob sie von „System.Exception“ abgeleitet sind oder nicht, in von „System.Exception“ abgeleitete C#-Assemblys weitergegeben. Das Ergebnis ist, dass System.Exception-Catch-Blöcke alle „vernünftig behandelten“ Ausnahmen abfangen, die nicht von früheren Blöcken abgefangen wurden. Wenn vor C# 1.0 jedoch eine nicht aus „System.Exception“ abgeleitete Ausnahme von einem Methodenaufruf ausgelöst wurde (der sich in einer nicht in C# geschriebenen Assembly befand), wurde sie nicht von einem „catch(System.Exception)“-Block abgefangen. Aus diesem Grund unterstützt C# auch einen allgemeinen Catch-Block „(catch{ })“, der sich jetzt identisch mit dem Block „catch(System.Exception Ausnahme)“ verhält, außer, dass er keinen Typ- oder Variablennamen aufweist. Der Nachteil eines solchen Blocks ist ganz einfach, dass es keine Ausnahmeinstanz gibt, auf die zugegriffen werden kann, und daher lässt sich auch keine sinnvolle Aktion ableiten. Es wäre nicht einmal möglich, die Ausnahme zu protokollieren oder den unwahrscheinlichen Fall zu erkennen, in dem eine solche Ausnahme harmlos ist.
In der Praxis sollten sowohl der „catch(System.Exception)“-Block als auch der allgemeine Catch-Block – der in diesem Artikel allgemein als „catch System.Exception“-Block bezeichnet wird – beide vermieden werden, außer unter dem Vorwand, eine Ausnahme durch Protokollieren zu „behandeln“, bevor der Prozess heruntergefahren wird. Dem allgemeinen Grundsatz folgend, nur Ausnahmen abzufangen, die Sie auch verarbeiten können, wäre es anmaßend, Code zu erstellen, für den der Programmierer erklärt „Dieser Catch kann jede und alle Ausnahmen abfangen, die jemals ausgelöst werden können“. Erstens wäre der Aufwand, wirklich alle Ausnahmen zu katalogisieren (insbesondere im Codeteil von „Main“, wo die Menge des ausgeführten Codes besonders groß ist und die Kontextinformationen besonders spärlich sind), monumental, außer bei wirklich ganz einfachen Programmen. Zweitens gibt es eine Unmenge von möglichen Ausnahmen, die unerwartet ausgelöst werden können.
Vor C# 4.0 gab es eine dritte Menge von Ausnahmen aufgrund von Fehlern bei der Statusintegrität, bei denen die Programmausführung generell nicht einmal fortgesetzt werden konnte. Diese Menge gibt jedoch seit C# 4.0 weniger Anlass zur Sorge, da „System.Exception“ (oder ein allgemeiner Catch-Block) solche Ausnahmen im Allgemeinen nicht abfangen. (Technisch gesehen, können Sie eine Methode mit „System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions“ ausstatten, sodass sogar diese Ausnahmen abgefangen werden, die Wahrscheinlichkeit, dass Sie sie anschließend in zureichender Weise verarbeiten können, ist aber sehr gering. Weitere Informationen finden Sie unter bit.ly/1FgeCU6.)
Ein technisches Detail, das zu Ausnahmen wegen Fehlern der Statusintegrität anzumerken ist, ist dass sie System.Exception-Catch-Blöcken nur entgehen können, wenn sie von der Runtime ausgelöst werden. Eine explizite Auslösung einer Ausnahme wegen eines Fehlers der Statusintegrität, wie etwa einer „System.StackOverflowException“ oder einer anderen „System.SystemException“, wird tatsächlich abgefangen. Solche Ausnahmen auszulösen wäre jedoch äußerst irreführend und wird wirklich nur aus Gründen der Abwärtskompatibilität unterstützt. Heute gilt die Richtlinie, dass keine der Systemintegritätsfehler-Ausnahmen ausgelöst werden sollen (einschließlich „System.StackOverflowException“, „System.SystemException“, „System.OutOfMemoryException“, „System.Runtime.InteropServices.COMException“, „System.Runtime.InteropServices.SEHException“ und „System.ExecutionEngineException“).
Zusammenfassend lässt sich sagen, vermeiden Sie die Verwendung eines „System.Exception“-Catch-Blocks, es sei denn, Sie möchten die Ausnahme mit Code zur Bereinigung behandeln und sie protokollieren, bevor Sie sie erneut auslösen oder die Anwendung geordnet schließen. Wenn der Catch-Block z. B. erfolgreich flüchtige Daten speichern könnte (wovon auch nicht unbedingt auszugehen ist, auch seine Integrität kann ja bereits beschädigt sein), bevor die Anwendung geschlossen oder die Ausnahme erneut ausgelöst wird. Wenn ein Szenario eintritt, in dem die Anwendung beendet werden sollte, weil die Fortsetzung der Ausführung unsicher wäre, sollte der Code die Methode „System.Environment.FailFast“ aufrufen. Vermeiden Sie „System.Exception“ und allgemeine Catch-Blöcke, außer zu dem Zweck, die Ausnahme vor dem Schließen der Anwendung ordentlich zu protokollieren.
Zusammenfassung
In diesem Artikel habe ich aktualisierte Richtlinien für die Ausnahmebehandlung vorgestellt – Abfangen von Ausnahmen, durch Verbesserungen an C# und dem .NET-Framework verursachte Aktualisierungen, die im Lauf der letzten Versionen stattgefunden haben. Trotz der Tatsache, dass es ein paar neue Richtlinien gibt, gelten viele noch so fest wie eh und je. Hier eine Zusammenfassung der Richtlinien für das Abfangen von Ausnahmen:
- VERMEIDEN Sie das Abfangen von Ausnahmen, die Sie nicht vollständig behandeln können.
- VERMEIDEN Sie das Verwerfen von Ausnahmen, die nicht vollständig behandelt wurden.
- VERWENDEN Sie „throw“ innerhalb von Catch-Blöcken, um eine Ausnahme erneut auszulösen, anstelle von „throw <Ausnahmeobjekt>“.
- LEGEN Sie die Eigenschaft „InnerException“ der umschließenden Ausnahme auf die abgefangene Ausnahme fest, es sei denn, dadurch werden private Daten zugänglich gemacht.
- ERWÄGEN Sie den Einsatz einer Ausnahmebedingung, um das erneute Auslösen von Ausnahmen, die Sie nach dem Abfangen nicht verarbeiten können, zu vermeiden.
- VERMEIDEN Sie das Auslösen von Ausnahmen aus Bedingungsausdrücken von Ausnahmen.
- SEIEN Sie beim erneuten Auslösen verschiedener Ausnahmen vorsichtig.
- Verwenden Sie „System.Exception“ und allgemeine Catch-Blöcke möglichst selten – außer zu dem Zweck, die Ausnahme vor dem Schließen der Anwendung zu protokollieren.
- VERMEIDEN Sie das Melden oder Protokollieren von Ausnahmen weiter unten in der Aufrufliste.
Eine Übersicht dieser Punkte im Detail finden Sie unter der itl.tc/ExceptionGuidelinesForCSharp. In einer kommenden Kolumne möchte ich mich mehr auf die Richtlinien für das Auslösen von Ausnahmen konzentrieren. Für den Moment soll dies im Zusammenhang mit dem Auslösen von Ausnahmen genügen: Der beabsichtigte Empfänger einer Ausnahme ist eher der Programmierer als der Endbenutzer eines Programms.
Beachten Sie, dass viel von diesem Material aus der nächsten Ausgabe meines Buchs „Essential C# 6.0 (5th Edition)“ (Addison-Wesley, 2015) stammt, das jetzt bei itl.tc/EssentialCSharp erhältlich ist.
Mark Michaelis ist der Gründer von IntelliTect und arbeitet als leitender technischer Architekt und Trainer. Seit fast zwei Jahrzehnten ist er ein Microsoft MVP und Microsoft-Regionalleiter seit 2007. Michaelis arbeitet in verschiedenen Microsoft-Softwareentwicklungs-Reviewteams mit, einschließlich C#, Microsoft Azure, SharePoint und Visual Studio ALM. Er hält häufig Vorträge bei Entwicklerkonferenzen und hat viele Bücher geschrieben, einschließlich seines letzten „Essential C# 6.0 (5th Edition)“. Sie können ihn auf Facebook unter facebook.com/Mark.Michaelis, über seinen Blog unter IntelliTect.com/Mark, auf Twitter: @markmichaelis oder per E-Mail unter mark@IntelliTect.com erreichen.
Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Kevin Bost, Jason Peterson und Mads Torgerson