Magischen Wert zurückgeben, Ausnahme auslösen oder bei einem Fehler false zurückgeben?

83

Manchmal muss ich eine Methode oder Eigenschaft für eine Klassenbibliothek schreiben, für die es nicht ungewöhnlich ist, keine echte Antwort zu haben, sondern einen Fehler. Etwas kann nicht bestimmt werden, ist nicht verfügbar, nicht gefunden, derzeit nicht möglich oder es sind keine Daten mehr verfügbar.

Ich denke, es gibt drei mögliche Lösungen für eine solche relativ nicht außergewöhnliche Situation, um auf ein Versagen in C # 4 hinzuweisen:

  • gib einen magischen Wert zurück, der sonst keine Bedeutung hat (wie nullund -1);
  • eine Ausnahme auslösen (zB KeyNotFoundException);
  • return falseund geben Sie den tatsächlichen Rückgabewert in einem outParameter an (z. B. Dictionary<,>.TryGetValue).

Die Fragen lauten also: In welcher nicht außergewöhnlichen Situation sollte ich eine Ausnahme auslösen? Und wenn ich nicht werfen sollte: Wann wird ein magischer Wert zurückgegeben, der über der Implementierung einer Try*Methode mit einem outParameter angegeben ist ? (Für mich outscheint der Parameter schmutzig zu sein, und es ist mehr Arbeit, ihn richtig zu verwenden.)

Ich suche nach sachlichen Antworten, z. B. Antworten mit Gestaltungsrichtlinien (ich kenne mich nicht mit Try*Methoden aus), Benutzerfreundlichkeit (da ich dies für eine Klassenbibliothek verlange), Konsistenz mit der BCL und Lesbarkeit.


In der .NET Framework-Basisklassenbibliothek werden alle drei Methoden verwendet:

Beachten Sie, dass es, wie Hashtablees zu der Zeit erstellt wurde, als es in C # keine Generika gab, einen magischen Wert verwendet objectund daher zurückgeben nullkann. Bei Generika werden jedoch Ausnahmen verwendet Dictionary<,>, und dies war anfangs nicht der Fall TryGetValue. Anscheinend ändern sich die Einsichten.

Offensichtlich gibt es die Item- TryGetValueund Parse- TryParseDualität aus einem Grund, daher gehe ich davon aus, dass das Auslösen von Ausnahmen für nicht außergewöhnliche Fehler in C # 4 nicht erfolgt . Die Try*Methoden existierten jedoch nicht immer, auch wenn sie Dictionary<,>.Itemexistierten.

Daniel AA Pelsmaeker
quelle
6
"Ausnahmen bedeuten Fehler". Das Nachfragen eines Wörterbuchs nach einem nicht vorhandenen Element ist ein Fehler. Bitten eines Streams zum Lesen von Daten, wenn es sich um eine EOF handelt, jedes Mal, wenn Sie einen Stream verwenden. (Dies fasst die lange schön formatierte Antwort zusammen, die ich nicht einreichen konnte :))
KutuluMike
6
Es ist nicht so, dass ich Ihre Frage für zu umfassend halte, sondern dass es sich nicht um eine konstruktive Frage handelt. Es gibt keine kanonische Antwort, da die Antworten bereits angezeigt werden.
George Stocker
2
@ GeorgeStocker Wenn die Antwort einfach und offensichtlich gewesen wäre, hätte ich die Frage nicht gestellt. Die Tatsache, dass sich die Leute darüber streiten können, warum eine bestimmte Auswahl unter allen Gesichtspunkten vorzuziehen ist (z. B. Leistung, Lesbarkeit, Benutzerfreundlichkeit, Wartbarkeit, Gestaltungsrichtlinien oder Konsistenz), macht sie inhärent nicht kanonisch und dennoch zu meiner Zufriedenheit verantwortlich. Alle Fragen können etwas subjektiv beantwortet werden. Anscheinend erwarten Sie, dass eine Frage meist ähnliche Antworten hat, damit sie eine gute Frage ist.
Daniel AA Pelsmaeker
3
@Virtlink: George ist ein von der Community gewählter Moderator, der viel Zeit für die Aufrechterhaltung des Stapelüberlaufs zur Verfügung stellt. Er gab an, warum er die Frage geschlossen hat, und die FAQ untermauern ihn.
Eric J.
15
Die Frage gehört hierher, nicht weil sie subjektiv ist, sondern weil sie konzeptionell ist. Faustregel: Wenn Sie vor Ihrem IDE-Code sitzen, fragen Sie ihn bei Stack Overflow. Wenn Sie vor einem Whiteboard-Brainstorming stehen, fragen Sie es hier bei Programmierern.
Robert Harvey

Antworten:

56

Ich denke nicht, dass Ihre Beispiele wirklich gleichwertig sind. Es gibt drei verschiedene Gruppen, von denen jede ihre eigenen Gründe für ihr Verhalten hat.

  1. Ein magischer Wert ist eine gute Option, wenn eine "Bis" -Zustand vorliegt, z. B. StreamReader.Readoder wenn ein einfach zu verwendender Wert vorliegt , der niemals eine gültige Antwort sein wird (-1 für IndexOf).
  2. Eine Ausnahme auslösen, wenn die Semantik der Funktion lautet, dass der Aufrufer sicher ist, dass sie funktioniert. In diesem Fall ist ein nicht vorhandener Schlüssel oder ein fehlerhaftes Doppelformat wirklich außergewöhnlich.
  3. Verwenden Sie einen out-Parameter und geben Sie einen Bool zurück, wenn die Semantik prüfen soll, ob die Operation möglich ist oder nicht.

Die Beispiele, die Sie angeben, sind für die Fälle 2 und 3 völlig klar. Für die magischen Werte kann argumentiert werden, ob dies eine gute Entwurfsentscheidung ist oder nicht in allen Fällen.

Der NaNRückgabewert von Math.Sqrtist ein Sonderfall - er folgt dem Gleitkomma-Standard.

Anders Abel
quelle
10
Nicht einverstanden mit Nummer 1. Magische Werte sind niemals eine gute Option, da der nächste Codierer die Bedeutung des magischen Werts kennen muss. Sie beeinträchtigen auch die Lesbarkeit. Ich kann mir keinen Fall vorstellen, in dem die Verwendung eines magischen Wertes besser ist als das Try-Muster.
0b101010
1
Aber was ist mit der Either<TLeft, TRight>Monade?
Sara
2
@ 0b101010: Ich habe nur einige Zeit damit verbracht, nachzuschauen, wie streamreader.read sicher -1 ...
jmoreno 18.02.18
33

Sie versuchen, dem Benutzer der API mitzuteilen, was er zu tun hat. Wenn Sie eine Ausnahme auslösen, müssen sie diese nicht abfangen, und nur durch Lesen der Dokumentation erfahren Sie, welche Möglichkeiten es gibt. Persönlich finde ich es langsam und mühsam, in der Dokumentation nach allen Ausnahmen zu suchen, die eine bestimmte Methode auslösen kann (selbst wenn es sich um Intellisense handelt, muss ich sie immer noch manuell herauskopieren).

Für einen magischen Wert müssen Sie weiterhin die Dokumentation lesen und möglicherweise auf eine constTabelle verweisen , um den Wert zu decodieren. Zumindest hat es nicht den Overhead einer Ausnahme für das, was Sie als nicht außergewöhnliches Ereignis bezeichnen.

Das ist der Grund, warum outich diese Methode mit der Try...Syntax bevorzuge, obwohl Parameter manchmal missbilligt werden . Es ist eine kanonische .NET- und C # -Syntax. Sie teilen dem Benutzer der API mit, dass er den Rückgabewert überprüfen muss, bevor er das Ergebnis verwendet. Sie können auch einen zweiten outParameter mit einer hilfreichen Fehlermeldung einfügen, die wiederum beim Debuggen hilft. Deshalb stimme ich für die Try...mit outParameter Lösung.

Eine andere Möglichkeit ist, ein spezielles "result" -Objekt zurückzugeben, obwohl ich das viel mühsamer finde:

interface IMyResult
{
    bool Success { get; }
    // Only access this if Success is true
    MyOtherClass Result { get; }
    // Only access this if Success is false
    string ErrorMessage { get; }
}

Dann sieht Ihre Funktion richtig aus, weil sie nur Eingabeparameter hat und nur eins zurückgibt. Es ist nur so, dass das eine, was es zurückgibt, ein Tupel ist.

In der Tat, wenn Sie in diese Art von Dingen sind, können Sie die neuen Tuple<>Klassen verwenden, die in .NET 4 eingeführt wurden. Persönlich mag ich die Tatsache nicht, dass die Bedeutung jedes Feldes weniger explizit ist, weil ich nicht geben kann Item1und Item2nützliche Namen.

Scott Whitlock
quelle
3
Persönlich verwende ich oft einen Ergebniscontainer wie Ihren, da IMyResultes möglich ist, ein komplexeres Ergebnis als nur trueoder falseoder den Ergebniswert zu kommunizieren . Try*()ist nur nützlich für einfache Dinge wie die Konvertierung von String in Int.
Ich selbst
1
Guter Post. Für mich bevorzuge ich die oben beschriebene Ergebnisstruktur-IDiom im Gegensatz zur Aufteilung zwischen "Rückgabewerten" und "Ausgangsparametern". Hält es ordentlich und ordentlich.
Ocean Airdrop
2
Das Problem mit dem Parameter second out besteht darin, dass Sie sich in einem komplexen Programm mit 50 Funktionen befinden. Wie können Sie diese Fehlermeldung dann an den Benutzer zurückmelden? Das Auslösen einer Ausnahme ist viel einfacher als das Überprüfen von Fehlern auf mehreren Ebenen. Wenn du eins bekommst, wirf es einfach und es spielt keine Rolle, wie tief du bist.
Brötchen
@rolls - Wenn wir Ausgabeobjekte verwenden, nehmen wir an, dass der unmittelbare Aufrufer mit der Ausgabe tun wird, was er will: lokal behandeln, ignorieren oder in die Luft sprudeln, indem er die in eine Ausnahme eingeschlossene Ausgabe wirft. Es ist das Beste aus beiden Wörtern - der Anrufer kann alle möglichen Ausgaben (mit Aufzählungen usw.) klar sehen, kann entscheiden, was mit dem Fehler geschehen soll, und muss nicht bei jedem Anruf erneut versuchen, ihn abzufangen. Wenn Sie meist erwarten, das Ergebnis sofort zu verarbeiten oder zu ignorieren, sind Rückgabeobjekte einfacher. Wenn Sie alle Fehler in die oberen Ebenen werfen möchten, sind Ausnahmen einfacher.
Drizin
2
Dies ist nur eine mühsamere Neuerfindung von geprüften Ausnahmen in C # als von geprüften Ausnahmen in Java.
Winter
17

Wie Ihre Beispiele bereits zeigen, muss jeder dieser Fälle separat bewertet werden, und zwischen "außergewöhnlichen Umständen" und "Flusskontrolle" besteht ein beträchtliches Spektrum an Graustufen, insbesondere wenn Ihre Methode wiederverwendbar sein soll und möglicherweise in ganz unterschiedlichen Mustern verwendet wird als es ursprünglich vorgesehen war. Erwarten Sie nicht, dass wir uns hier alle darauf einigen, was "nicht außergewöhnlich" bedeutet, insbesondere wenn Sie sofort die Möglichkeit diskutieren, "Ausnahmen" zu verwenden, um dies umzusetzen.

Wir sind uns vielleicht auch nicht einig darüber, durch welches Design der Code am einfachsten zu lesen und zu warten ist, aber ich gehe davon aus, dass der Bibliotheksdesigner eine klare persönliche Vision davon hat und diese nur mit den anderen Überlegungen in Einklang bringen muss.

Kurze Antwort

Folgen Sie Ihrem Bauchgefühl, es sei denn, Sie entwerfen recht schnelle Methoden und erwarten die Möglichkeit einer unvorhergesehenen Wiederverwendung.

Lange Antwort

Jeder zukünftige Anrufer kann frei zwischen Fehlercodes und Ausnahmen in beide Richtungen übersetzen. Dies macht die beiden Entwurfsansätze bis auf Leistung, Debuggerfreundlichkeit und einige eingeschränkte Interoperabilitätskontexte nahezu gleich. Das läuft normalerweise auf Leistung hinaus, also konzentrieren wir uns darauf.

  • Als Faustregel gilt, dass das Auslösen einer Ausnahme 200x langsamer ist als eine reguläre Rückkehr (in Wirklichkeit gibt es eine signifikante Abweichung davon).

  • Eine andere Faustregel ist, dass das Auslösen einer Ausnahme im Vergleich zu den gröbsten magischen Werten häufig einen deutlich saubereren Code ermöglicht, da Sie sich nicht darauf verlassen müssen, dass der Programmierer den Fehlercode in einen anderen Fehlercode übersetzt, da er durch mehrere Client-Code-Ebenen in Richtung wandert Ein Punkt, an dem genügend Kontext vorhanden ist, um ihn konsistent und angemessen zu behandeln. (Sonderfall: nullNeigt dazu, hier besser abzuschneiden als bei anderen magischen Werten, da sie dazu neigen, sich NullReferenceExceptionbei einigen, aber nicht allen Arten von Fehlern automatisch in einen zu übersetzen ; normalerweise, aber nicht immer, recht nahe an der Fehlerquelle. )

Was ist die Lektion?

Verwenden Sie für eine Funktion, die nur einige Male während der Lebensdauer einer Anwendung aufgerufen wird (z. B. die App-Initialisierung), alles, was Sie zu einem saubereren und besser verständlichen Code führt. Leistung kann kein Problem sein.

Verwenden Sie für eine Einwegfunktion alles, was Ihnen einen saubereren Code bietet. Führen Sie dann eine Profilerstellung durch (falls erforderlich) und ändern Sie die Ausnahmen, um Codes zurückzugeben, wenn sie aufgrund von Messungen oder der gesamten Programmstruktur zu den vermuteten oberen Engpässen gehören.

Verwenden Sie für eine teure wiederverwendbare Funktion alles, was Ihnen saubereren Code bietet. Wenn Sie grundsätzlich immer einen Netzwerk-Roundtrip durchführen oder eine XML-Datei auf der Festplatte analysieren müssen, ist der Aufwand für das Auslösen einer Ausnahme wahrscheinlich vernachlässigbar. Es ist wichtiger, nicht einmal versehentlich Details eines Fehlers zu verlieren, als besonders schnell von einem "nicht außergewöhnlichen Fehler" zurückzukehren.

Eine schlanke wiederverwendbare Funktion erfordert mehr Nachdenken. Durch die Verwendung von Ausnahmen erzwingen Sie eine 100-fache Verzögerung für Anrufer, die die Ausnahme bei der Hälfte ihrer (vielen) Aufrufe sehen, wenn der Funktionskörper sehr schnell ausgeführt wird. Ausnahmen sind immer noch eine Gestaltungsoption, aber Sie müssen Anrufern, die sich dies nicht leisten können, eine Alternative mit geringem Overhead anbieten. Schauen wir uns ein Beispiel an.

Sie führen ein großartiges Beispiel dafür an Dictionary<,>.Item, das sich lose gesagt von der Rückgabe von nullWerten zum Werfen KeyNotFoundExceptionzwischen .NET 1.1 und .NET 2.0 geändert hat (nur, wenn Sie bereit sind, es als Hashtable.Itempraktischen, nicht generischen Vorläufer zu betrachten). Der Grund dieser "Veränderung" ist hier nicht ohne Interesse. Die Leistungsoptimierung von Werttypen (kein Boxen mehr) machte den ursprünglichen Zauberwert ( null) zu einer Nichtoption. outParameter würden nur einen kleinen Teil der Leistungskosten zurückbringen. Diese letztere Leistungsüberlegung ist im Vergleich zum Aufwand für das Werfen von a völlig vernachlässigbarKeyNotFoundException , aber das Ausnahmedesign ist hier immer noch überlegen. Warum?

  • ref / out-Parameter verursachen jedes Mal Kosten, nicht nur im "Fehler" -Fall
  • Jeder, der sich interessiert, kann Containsvor jedem Aufruf den Indexer aufrufen, und dieses Muster liest sich ganz natürlich. Wenn ein Entwickler den Aufruf jedoch vergessen möchte Contains, können sich keine Leistungsprobleme einschleichen. KeyNotFoundExceptionist laut genug, um bemerkt und repariert zu werden.
Jirka Hanika
quelle
Ich denke, 200x ist optimistisch in Bezug auf das Auftreten von Ausnahmen ... siehe blogs.msdn.com/b/cbrumme/archive/2003/10/01/51524.aspx Abschnitt "Leistung und Trends" kurz vor den Kommentaren.
gbjbaanb
@ gbjbaanb - Na ja, vielleicht. In diesem Artikel wird eine Ausfallrate von 1% verwendet, um das Thema zu diskutieren, bei dem es sich nicht um einen ganz anderen Standard handelt. Meine eigenen Gedanken und vage in Erinnerung gebliebenen Messungen stammen aus dem Kontext von tabellenimplementiertem C ++ (siehe Abschnitt 5.4.1.2 dieses Berichts , in dem ein Problem darin besteht, dass die erste Ausnahme dieser Art wahrscheinlich mit einem Seitenfehler (drastisch und variabel) beginnt Aber ich werde ein Experiment mit .NET 4 durchführen und veröffentlichen und möglicherweise diesen Standardwert optimieren. Ich betone bereits die Varianz.
Jirka Hanika
Sind die Kosten für Ref / Out-Parameter dann so hoch ? Wie? Ein Aufruf Containsvor einem Aufruf eines Indexers kann zu einer Race-Bedingung führen, die nicht vorliegen muss TryGetValue.
Daniel AA Pelsmaeker
@ gbjbaanb - Experimentiert fertig. Ich war faul und habe Mono unter Linux verwendet. Ausnahmen gaben mir ~ 563000 Würfe in 3 Sekunden. Rückkehr gab mir ~ 10900000 Rückkehr in 3 Sekunden. Das ist 1:20, nicht einmal 1: 200. Ich empfehle immer noch, 1: 100+ für realistischeren Code zu denken. (Unsere Param-Variante hatte, wie ich vorhergesagt hatte, vernachlässigbare Kosten - ich vermute tatsächlich, dass der Jitter den Anruf in meinem minimalistischen Beispiel wahrscheinlich vollständig optimiert hatte, wenn keine Ausnahme geworfen wurde, unabhängig von der Signatur.)
Jirka Hanika
@Virtlink - Einig in Bezug auf die Thread-Sicherheit im Allgemeinen, aber angesichts der Tatsache, dass Sie sich insbesondere auf .NET 4 bezogen haben, sollten Sie ConcurrentDictionaryfür einen Multithread-Zugriff und Dictionaryfür einen Single-Thread-Zugriff für eine optimale Leistung verwenden. Das heißt, wenn `` Contains` nicht verwendet wird, ist der Code-Thread für diese bestimmte DictionaryKlasse NICHT sicher .
Jirka Hanika
10

Was ist das Beste, was in einer relativ nicht außergewöhnlichen Situation zu tun ist, um auf ein Versagen hinzuweisen, und warum?

Sie sollten keinen Fehler zulassen.

Ich weiß, es ist handgewellt und idealistisch, aber hör mir zu. Beim Entwerfen gibt es eine Reihe von Fällen, in denen Sie die Möglichkeit haben, eine Version ohne Fehlermodi zu bevorzugen. Anstatt ein 'FindAll' zu haben, das fehlschlägt, verwendet LINQ eine where-Klausel, die einfach eine leere Aufzählung zurückgibt. Anstatt ein Objekt zu haben, das vor der Verwendung initialisiert werden muss, lassen Sie den Konstruktor das Objekt initialisieren (oder initialisieren, wenn das nicht initialisierte erkannt wird). Der Schlüssel ist das Entfernen des Fehlerzweigs im Consumer-Code. Dies ist das Problem, also konzentrieren Sie sich darauf.

Eine andere Strategie hierfür ist das KeyNotFoundsscenario. In fast jeder Codebasis, an der ich seit 3.0 gearbeitet habe, gibt es so etwas wie diese Erweiterungsmethode:

public static class DictionaryExtensions {
    public static V GetValue<K, V>(this IDictionary<K, V> arg, K key, Func<K,V> ifNotFound) {
        if (!arg.ContainsKey(key)) {
            return ifNotFound(key);
        }

        return arg[key];
    }
}

Hierfür gibt es keinen echten Fehlermodus. ConcurrentDictionaryhat ein ähnliches GetOrAddeingebaut.

Trotzdem wird es immer Zeiten geben, in denen es einfach unvermeidbar ist. Alle drei haben ihren Platz, aber ich würde die erste Option bevorzugen. Trotz allem, was aus der Gefahr von Null gemacht wird, ist es gut bekannt und passt in viele der Szenarien "Element nicht gefunden" oder "Ergebnis ist nicht anwendbar", die den Satz "Nicht außergewöhnlicher Fehler" ausmachen. Insbesondere wenn Sie nullbare Werttypen erstellen, ist die Bedeutung von "Dies könnte fehlschlagen" im Code sehr deutlich und schwer zu vergessen / zu vermasseln.

Die zweite Option ist gut genug, wenn Ihr Benutzer etwas Dummes tut. Gibt Ihnen einen String mit dem falschen Format, versucht, das Datum auf den 42. Dezember zu setzen ... etwas, das beim Testen schnell und spektakulär in die Luft gejagt werden soll, damit fehlerhafter Code erkannt und behoben wird.

Die letzte Option ist eine, die ich zunehmend ablehne. Unsere Parameter sind umständlich und verletzen häufig einige der besten Methoden bei der Erstellung von Methoden, z. B. sich auf eine Sache zu konzentrieren und keine Nebenwirkungen zu haben. Außerdem ist der out-Parameter normalerweise nur während des Erfolgs von Bedeutung. Sie sind jedoch für bestimmte Vorgänge unerlässlich, die in der Regel durch Nebenläufigkeitsprobleme oder Leistungsaspekte eingeschränkt sind (z. B. wenn Sie keine zweite Reise in die Datenbank durchführen möchten).

Wenn der Rückgabewert und der Parameter out nicht trivial sind, wird Scott Whitlocks Vorschlag für ein Ergebnisobjekt bevorzugt (wie die MatchKlasse von Regex ).

Telastyn
quelle
7
Pet peeve hier: outParameter sind völlig orthogonal zum Problem der Nebenwirkungen. Das Ändern eines refParameters ist ein Nebeneffekt, und das Ändern des Status eines Objekts, den Sie über einen Eingabeparameter übergeben, ist ein Nebeneffekt. Ein outParameter ist jedoch nur eine umständliche Methode, um eine Funktion dazu zu bringen, mehr als einen Wert zurückzugeben. Es gibt keine Nebenwirkung, nur mehrere Rückgabewerte.
Scott Whitlock
Ich sagte eher , weil die Leute sie benutzen. Wie Sie sagen, es sind einfach mehrere Rückgabewerte.
Telastyn
Aber wenn Sie Parameter nicht mögen und Ausnahmen verwenden, um Dinge spektakulär in die Luft zu jagen, wenn das Format falsch ist ... wie gehen Sie dann mit dem Fall um, in dem Ihr Format vom Benutzer eingegeben wird? Dann kann entweder ein Benutzer Dinge in die Luft jagen, oder es entsteht die Leistungsstrafe, eine Ausnahme zu werfen und dann zu fangen. Richtig?
Daniel AA Pelsmaeker
@virtlink durch eine eindeutige Validierungsmethode. Sie müssen es auf jeden Fall verwenden , um der Benutzeroberfläche ordnungsgemäße Nachrichten bereitzustellen, bevor sie es senden.
Telastyn
1
Es gibt ein legitimes Muster für out-Parameter, und das ist eine Funktion mit Überladungen, die verschiedene Typen zurückgeben. Die Überladungsauflösung funktioniert nicht für Rückgabetypen, aber für Out-Parameter.
Robert Harvey
2

Immer lieber eine Ausnahme auslösen. Es verfügt über eine einheitliche Oberfläche für alle Funktionen, die ausfallen könnten, und zeigt Fehler so laut wie möglich an - eine sehr wünschenswerte Eigenschaft.

Beachten Sie, dass Parseund TryParsesind nicht wirklich die gleiche Sache abgesehen von Ausfallmodi. Die Tatsache, dass TryParseauch der Wert zurückgegeben werden kann, ist eigentlich rechtwinklig. Stellen Sie sich zum Beispiel die Situation vor, in der Sie Eingaben validieren. Es ist Ihnen eigentlich egal, wie hoch der Wert ist, solange er gültig ist. Und es ist nichts Falsches daran, eine Art IsValidFor(arguments)Funktion anzubieten . Es kann jedoch niemals die primäre Betriebsart sein.

DeadMG
quelle
4
Wenn Sie mit einer großen Anzahl von Aufrufen einer Methode zu tun haben, können Ausnahmen die Leistung erheblich beeinträchtigen. Ausnahmen sollten für Ausnahmebedingungen reserviert werden und wären für die Validierung von Formulareingaben durchaus akzeptabel, nicht jedoch für das Parsen von Zahlen aus großen Dateien.
Robert Harvey
1
Das ist ein spezielleres Bedürfnis, nicht der allgemeine Fall.
DeadMG
2
Also sagst du. Aber Sie haben das Wort "immer" verwendet. :)
Robert Harvey
@DeadMG, stimmte mit RobertHarvey überein, obwohl ich der Meinung bin, dass die Antwort übermäßig abgelehnt wurde, wenn sie so geändert wurde, dass sie "die meiste Zeit" widerspiegelt, und dann Ausnahmen (kein Wortspiel beabsichtigt) für den allgemeinen Fall aufzeigt, wenn es um häufig verwendete Hochleistung geht ruft dazu auf, andere Optionen in Betracht zu ziehen.
Gerald Davis
Ausnahmen sind nicht teuer. Das Abfangen tiefgreifender Ausnahmen kann teuer sein, da das System den Stapel bis zum nächsten kritischen Punkt abwickeln muss. Aber das "teuer" ist relativ winzig und sollte auch in engen verschachtelten Schleifen nicht befürchtet werden.
Matthew Whited
2

Wie andere angemerkt haben, ist der magische Wert (einschließlich eines booleschen Rückgabewerts) keine besonders gute Lösung, außer als "Bereichsende" -Marker. Grund: Die Semantik ist nicht explizit, auch wenn Sie die Methoden des Objekts untersuchen. Sie müssen tatsächlich die vollständige Dokumentation für das gesamte Objekt bis zu "oh ja, wenn es -42 zurückgibt, bedeutet das bla bla bla" lesen.

Diese Lösung kann aus historischen Gründen oder aus Leistungsgründen verwendet werden, sollte jedoch ansonsten vermieden werden.

Dies hinterlässt zwei allgemeine Fälle: Prüfen oder Ausnahmen.

Hier gilt die Faustregel, dass das Programm nur dann auf Ausnahmen reagieren soll, wenn das Programm / ungewollt / gegen eine Bedingung verstößt. Das Prüfen sollte verwendet werden, um sicherzustellen, dass dies nicht geschieht. Eine Ausnahme bedeutet daher entweder, dass die entsprechende Prüfung nicht im Voraus durchgeführt wurde oder dass etwas völlig Unerwartetes passiert ist.

Beispiel:

Sie möchten eine Datei aus einem bestimmten Pfad erstellen.

Sie sollten das File-Objekt verwenden, um vorab zu prüfen, ob dieser Pfad für die Erstellung oder das Schreiben von Dateien zulässig ist.

Wenn Ihr Programm dennoch versucht, auf einen Pfad zu schreiben, der illegal oder auf andere Weise nicht beschreibbar ist, sollten Sie eine Exzaption erhalten. Dies könnte aufgrund einer Racebedingung passieren (ein anderer Benutzer hat das Verzeichnis entfernt oder es nach der Prüfung schreibgeschützt gemacht).

Die Aufgabe, einen unerwarteten Fehler (signalisiert durch eine Ausnahme) zu behandeln und zu prüfen, ob die Bedingungen für die Operation im Voraus richtig sind (Sondierung), ist normalerweise unterschiedlich strukturiert und sollte daher unterschiedliche Mechanismen verwenden.

Anders Johansen
quelle
0

Ich denke, das TryMuster ist die beste Wahl, wenn der Code nur angibt, was passiert ist. Ich hasse param und mag nullable Objekt. Ich habe folgende Klasse erstellt

public sealed class Bag<TValue>
{
    public Bag(TValue value, bool hasValue = true)
    {
        HasValue = hasValue;
        Value = value;
    }

    public static Bag<TValue> Empty
    {
        get { return new Bag<TValue>(default(TValue), false); }
    }

    public bool HasValue { get; private set; }
    public TValue Value { get; private set; }
}

so kann ich folgenden code schreiben

    public static Bag<XElement> GetXElement(this XElement element, string elementName)
    {
        try
        {
            XElement result = element.Element(elementName);
            return result == null
                       ? Bag<XElement>.Empty
                       : new Bag<XElement>(result);
        }
        catch (Exception)
        {
            return Bag<XElement>.Empty;
        }
    }

Sieht aus wie nullbar, aber nicht nur für den Werttyp

Ein anderes Beispiel

    public static Bag<string> TryParseString(this XElement element, string attributeName)
    {
        Bag<string> attributeResult = GetString(element, attributeName);
        if (attributeResult.HasValue)
        {
            return new Bag<string>(attributeResult.Value);
        }
        return Bag<string>.Empty;
    }

    private static Bag<string> GetString(XElement element, string attributeName)
    {
        try
        {
            string result = element.GetAttribute(attributeName).Value;
            return new Bag<string>(result);
        }
        catch (Exception)
        {
            return Bag<string>.Empty;
        }
    }
GSerjo
quelle
3
try catchWenn Sie anrufen GetXElement()und mehrmals ausfallen, wird dies Ihre Leistung in Mitleidenschaft ziehen .
Robert Harvey
Manchmal ist es egal. Werfen Sie einen Blick auf Bag Call. Vielen Dank für Ihre Beobachtung
Ihre Tasche <T> Klasse ist fast identisch mit System.Nullable <T> aka "nullable Objekt"
Aeroson
ja, fast public struct Nullable<T> where T : structder Hauptunterschied in einer Einschränkung. Übrigens ist die neueste Version hier github.com/Nelibur/Nelibur/blob/master/Source/Nelibur.Sword/…
GSerjo
0

Wenn Sie an der Route "magischer Wert" interessiert sind, besteht eine weitere Möglichkeit zur Lösung darin, den Zweck der Lazy-Klasse zu überladen. Obwohl Lazy die Instantiierung aufschieben soll, hindert Sie nichts wirklich daran, wie ein Vielleicht oder eine Option zu verwenden. Zum Beispiel:

    public static Lazy<TValue> GetValue<TValue, TKey>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key)
    {
        TValue retVal;
        if (dictionary.TryGetValue(key, out retVal))
        {
            var retValRef = retVal;
            var lazy = new Lazy<TValue>(() => retValRef);
            retVal = lazy.Value;
            return lazy;
        }

        return new Lazy<TValue>(() => default(TValue));
    }
Zumalifeguard
quelle