Wann ist es angebracht, eine Ausnahme aus einem Property Getter oder Setter heraus auszulösen? Wann ist es nicht angebracht? Warum? Links zu externen Dokumenten zu diesem Thema wären hilfreich ... Google ist überraschend wenig aufgetaucht.
Wann ist es angebracht, eine Ausnahme aus einem Property Getter oder Setter heraus auszulösen? Wann ist es nicht angebracht? Warum? Links zu externen Dokumenten zu diesem Thema wären hilfreich ... Google ist überraschend wenig aufgetaucht.
Antworten:
Microsoft hat seine Empfehlungen zum Entwerfen von Eigenschaften unter http://msdn.microsoft.com/en-us/library/ms229006.aspx
Im Wesentlichen empfehlen sie, dass Property Getter leichte Accessoren sind, die immer sicher angerufen werden können. Sie empfehlen, Getter als Methoden neu zu gestalten, wenn Sie Ausnahmen auslösen müssen. Für Setter geben sie an, dass Ausnahmen eine angemessene und akzeptable Fehlerbehandlungsstrategie sind.
Für Indexer gibt Microsoft an, dass es sowohl für Getter als auch für Setter akzeptabel ist, Ausnahmen auszulösen. Tatsächlich tun dies viele Indexer in der .NET-Bibliothek. Die häufigste Ausnahme ist ArgumentOutOfRangeException
.
Es gibt einige ziemlich gute Gründe, warum Sie keine Ausnahmen in Property Getter auslösen möchten:
obj.PropA.AnotherProp.YetAnother
- Bei dieser Art von Syntax wird es problematisch zu entscheiden, wo Ausnahme-Catch-Anweisungen eingefügt werden sollen.Als Randnotiz sollte man sich bewusst sein, dass nur weil eine Eigenschaft nicht dafür ausgelegt ist , eine Ausnahme auszulösen, dies nicht bedeutet, dass dies nicht der Fall ist. Es könnte leicht sein, Code aufzurufen, der dies tut. Selbst das einfache Zuweisen eines neuen Objekts (wie einer Zeichenfolge) kann zu Ausnahmen führen. Sie sollten Ihren Code immer defensiv schreiben und Ausnahmen von allem erwarten, was Sie aufrufen.
Es ist nichts Falsches daran, Ausnahmen von Setzern zu werfen. Wie kann man besser anzeigen, dass der Wert für eine bestimmte Eigenschaft nicht gültig ist?
Für Getter ist es im Allgemeinen verpönt, und das kann ziemlich einfach erklärt werden: Ein Getter von Eigenschaften meldet im Allgemeinen den aktuellen Zustand eines Objekts; Daher ist der einzige Fall, in dem es für einen Getter sinnvoll ist, zu werfen, wenn der Zustand ungültig ist. Es wird jedoch auch allgemein als eine gute Idee angesehen, Ihre Klassen so zu gestalten, dass es einfach nicht möglich ist, ein ungültiges Objekt anfangs abzurufen oder es mit normalen Mitteln in einen ungültigen Zustand zu versetzen (dh immer eine vollständige Initialisierung in Konstruktoren sicherzustellen und Versuchen Sie, Methoden in Bezug auf Zustandsgültigkeit und Klasseninvarianten ausnahmesicher zu machen. Solange Sie sich an diese Regel halten, sollten Ihre Immobilienbeschaffer niemals in eine Situation geraten, in der sie einen ungültigen Zustand melden müssen, und daher niemals werfen.
Es gibt eine Ausnahme, die ich kenne, und es ist tatsächlich eine ziemlich große: jedes Objekt, das implementiert wird IDisposable
. Dispose
ist speziell dazu gedacht, Objekte in einen ungültigen Zustand zu versetzen, und es gibt sogar eine spezielle Ausnahmeklasse, ObjectDisposedException
die in diesem Fall verwendet werden kann. Es ist völlig normal, ObjectDisposedException
von jedem Klassenmitglied, einschließlich Property Getter (und ohne sich Dispose
selbst), zu werfen , nachdem das Objekt entsorgt wurde.
IDisposable
nach a unbrauchbar gemacht werden sollten Dispose
. Wenn das Aufrufen eines Mitglieds die Verwendung einer Ressource erfordern würde, die Dispose
nicht verfügbar geworden ist (z. B. würde das Mitglied Daten aus einem geschlossenen Stream lesen), sollte das Mitglied werfen, ObjectDisposedException
anstatt zu verlieren, z. B. ArgumentException
wenn eines ein Formular mit Eigenschaften hat, die das darstellen Werte in bestimmten Feldern, scheint es weitaus hilfreicher zu sein, solche Eigenschaften nach der Entsorgung lesen zu lassen (was die zuletzt eingegebenen Werte ergibt), als ...
Dispose
verschoben werden, bis alle diese Eigenschaften gelesen wurden. In einigen Fällen, in denen ein Thread möglicherweise blockierende Lesevorgänge für ein Objekt verwendet, während ein anderer es schließt, und in denen Daten zu einem beliebigen Zeitpunkt zuvor eintreffen Dispose
, kann es hilfreich sein Dispose
, eingehende Daten abgeschnitten zu haben , aber zuvor gelesene Daten lesen zu lassen. Man sollte keine künstliche Unterscheidung zwischen Close
und Dispose
in Situationen erzwingen, in denen sonst keine existieren müsste.
Get...
Methode. Eine Ausnahme ist hier, wenn Sie eine vorhandene Schnittstelle implementieren müssen, für die Sie eine Eigenschaft angeben müssen.
Es ist fast nie für einen Getter geeignet und manchmal auch für einen Setter.
Die beste Quelle für diese Art von Fragen sind "Framework Design Guidelines" von Cwalina und Abrams; Es ist als gebundenes Buch erhältlich und große Teile davon sind auch online erhältlich.
Ab Abschnitt 5.2: Eigenschaftsgestaltung
Vermeiden Sie es, Ausnahmen von Immobilien-Gettern zu werfen. Property Getter sollten einfache Operationen sein und keine Voraussetzungen haben. Wenn ein Getter eine Ausnahme auslösen kann, sollte sie wahrscheinlich als Methode neu gestaltet werden. Beachten Sie, dass diese Regel nicht für Indexer gilt, bei denen wir aufgrund der Validierung der Argumente Ausnahmen erwarten.
Beachten Sie, dass diese Richtlinie nur für Getter gilt. Es ist in Ordnung, eine Ausnahme in einem Eigenschaftssetter auszulösen.
ObjectDisposedException
wenn das Objekt Dispose()
aufgerufen wurde und anschließend etwas nach einem Eigenschaftswert fragt? Es scheint, als sollte die Anleitung lauten: "Vermeiden Sie das Auslösen von Ausnahmen von Eigenschaftsträgern, es sei denn, das Objekt wurde entsorgt. In diesem Fall sollten Sie in Betracht ziehen, eine ObjectDisposedExcpetion auszulösen."
Ein guter Ansatz für Ausnahmen besteht darin, sie zu verwenden, um Code für sich und andere Entwickler wie folgt zu dokumentieren:
Ausnahmen sollten für außergewöhnliche Programmzustände gelten. Das heißt, es ist in Ordnung, sie zu schreiben, wo immer Sie wollen!
Ein Grund, warum Sie sie in Getter einfügen möchten, ist die Dokumentation der API einer Klasse. Wenn die Software eine Ausnahme auslöst, sobald ein Programmierer versucht, sie falsch zu verwenden, wird sie nicht falsch verwendet! Wenn Sie beispielsweise während eines Datenlesevorgangs eine Validierung haben, ist es möglicherweise nicht sinnvoll, fortzufahren und auf die Ergebnisse des Prozesses zuzugreifen, wenn schwerwiegende Fehler in den Daten aufgetreten sind. In diesem Fall möchten Sie möglicherweise den Ausgabewurf abrufen, wenn Fehler aufgetreten sind, um sicherzustellen, dass ein anderer Programmierer nach dieser Bedingung sucht.
Sie sind eine Möglichkeit, die Annahmen und Grenzen eines Subsystems / einer Methode / was auch immer zu dokumentieren. Im allgemeinen Fall sollten sie nicht gefangen werden! Dies liegt auch daran, dass sie niemals ausgelöst werden, wenn das System wie erwartet zusammenarbeitet: Wenn eine Ausnahme auftritt, zeigt dies, dass die Annahmen eines Codeteils nicht erfüllt sind - z. B. dass es nicht auf die Art und Weise mit der Welt um es herum interagiert es war ursprünglich beabsichtigt. Wenn Sie eine Ausnahme abfangen, die zu diesem Zweck geschrieben wurde, bedeutet dies wahrscheinlich, dass das System in einen unvorhersehbaren / inkonsistenten Zustand eingetreten ist. Dies kann letztendlich zu einem Absturz oder einer Beschädigung von Daten oder Ähnlichem führen, was wahrscheinlich viel schwieriger zu erkennen / zu debuggen ist.
Ausnahmemeldungen sind eine sehr grobe Methode, um Fehler zu melden. Sie können nicht massenweise erfasst werden und enthalten nur eine Zeichenfolge. Dies macht sie ungeeignet für die Meldung von Problemen in Eingabedaten. Im Normalbetrieb sollte das System selbst keinen Fehlerzustand aufweisen. Aus diesem Grund sollten die darin enthaltenen Nachrichten für Programmierer und nicht für Benutzer konzipiert sein. Fehlerhafte Eingabedaten können erkannt und in geeigneteren (benutzerdefinierten) Formaten an Benutzer weitergeleitet werden.
Die Ausnahme (haha!) Von dieser Regel sind Dinge wie IO, bei denen Ausnahmen nicht unter Ihrer Kontrolle stehen und nicht im Voraus überprüft werden können.
Dies ist alles in MSDN dokumentiert (wie in anderen Antworten verlinkt), aber hier ist eine allgemeine Faustregel ...
Im Setter, wenn Ihre Eigenschaft über den Typ hinaus validiert werden soll. Beispielsweise sollte eine Eigenschaft namens PhoneNumber wahrscheinlich eine Regex-Validierung haben und einen Fehler auslösen, wenn das Format nicht gültig ist.
Für Getter, möglicherweise wenn der Wert null ist, aber höchstwahrscheinlich ist dies etwas, das Sie für den aufrufenden Code (gemäß den Entwurfsrichtlinien) behandeln möchten.
MSDN: Standardausnahmetypen abfangen und auslösen
Dies ist eine sehr komplexe Frage und Antwort hängt davon ab, wie Ihr Objekt verwendet wird. Als Faustregel gilt, dass Property Getter und Setter, bei denen es sich um "späte Bindung" handelt, keine Ausnahmen auslösen sollten, während Eigenschaften mit ausschließlich "früher Bindung" bei Bedarf Ausnahmen auslösen sollten. Übrigens definiert das Code-Analyse-Tool von Microsoft die Verwendung von Eigenschaften meiner Meinung nach zu eng.
"späte Bindung" bedeutet, dass Eigenschaften durch Reflexion gefunden werden. Zum Beispiel wird das Attribut "Serialisierbar" verwendet, um ein Objekt über seine Eigenschaften zu serialisieren / deserialisieren. Das Auslösen einer Ausnahme in einer solchen Situation führt zu einer katastrophalen Unterbrechung und ist keine gute Möglichkeit, Ausnahmen zu verwenden, um robusteren Code zu erstellen.
"Early Binding" bedeutet, dass eine Eigenschaftsverwendung vom Compiler im Code gebunden wird. Zum Beispiel, wenn ein von Ihnen geschriebener Code auf einen Eigenschafts-Getter verweist. In diesem Fall ist es in Ordnung, Ausnahmen auszulösen, wenn sie sinnvoll sind.
Ein Objekt mit internen Attributen hat einen Status, der durch die Werte dieser Attribute bestimmt wird. Eigenschaften, die Attribute ausdrücken, die den internen Status des Objekts kennen und darauf reagieren, sollten nicht für die späte Bindung verwendet werden. Angenommen, Sie haben ein Objekt, das geöffnet, aufgerufen und dann geschlossen werden muss. In diesem Fall sollte der Zugriff auf die Eigenschaften, ohne zuerst open aufzurufen, zu einer Ausnahme führen. Nehmen wir in diesem Fall an, wir lösen keine Ausnahme aus und erlauben dem Code den Zugriff auf einen Wert, ohne eine Ausnahme auszulösen. Der Code wird glücklich erscheinen, obwohl er einen Wert von einem Getter erhalten hat, der nicht sinnvoll ist. Jetzt haben wir den Code, der den Getter aufgerufen hat, in eine schlechte Situation gebracht, da er wissen muss, wie der Wert überprüft wird, um festzustellen, ob er nicht sinnvoll ist. Dies bedeutet, dass der Code Annahmen über den Wert treffen muss, den er vom Property Getter erhalten hat, um ihn zu validieren. So wird schlechter Code geschrieben.
Ich hatte diesen Code, bei dem ich mir nicht sicher war, welche Ausnahme ich auslösen sollte.
public Person
{
public string Name { get; set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
if (person.Name == null) {
throw new Exception("Name of person is null.");
// I was unsure of which exception to throw here.
}
Console.WriteLine("Name is: " + person.Name);
}
Ich habe verhindert, dass das Modell die Eigenschaft überhaupt null hat, indem ich es als Argument im Konstruktor erzwungen habe.
public Person
{
public Person(string name)
{
if (name == null) {
throw new ArgumentNullException(nameof(name));
}
Name = name;
}
public string Name { get; private set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
Console.WriteLine("Name is: " + person.Name);
}