it-swarm.dev

Validierung von Konstruktorparametern in C # - Best Practices

Was ist die beste Vorgehensweise für die Validierung von Konstruktorparametern?

Angenommen, ein einfaches Stück C #:

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

Wäre es akzeptabel, eine Ausnahme auszulösen?

Die Alternative, auf die ich gestoßen bin, war die Vorvalidierung vor dem Instanziieren:

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}
35
MPelletier

Ich neige dazu, meine gesamte Validierung im Konstruktor durchzuführen. Dies ist ein Muss, da ich fast immer unveränderliche Objekte erstelle. Für Ihren speziellen Fall halte ich dies für akzeptabel.

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

Wenn Sie .NET 4 verwenden, können Sie dies tun. Dies hängt natürlich davon ab, ob Sie eine Zeichenfolge, die nur Leerzeichen enthält, als ungültig betrachten.

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");
27
ChaosPandion

Viele Leute sagen, dass Konstrukteure keine Ausnahmen auslösen sollten. KyleG auf diese Seite macht zum Beispiel genau das. Ehrlich gesagt, ich kann mir keinen Grund vorstellen, warum nicht.

In C++ ist es eine schlechte Idee, eine Ausnahme von einem Konstruktor auszulösen, da Ihnen der zugewiesene Speicher verbleibt, der ein nicht initialisiertes Objekt enthält, auf das Sie keinen Verweis haben (dh es handelt sich um einen klassischen Speicherverlust). Das ist wahrscheinlich der Grund für das Stigma - eine Gruppe von C++ - Entwicklern der alten Schule hat ihr C # -Lernen halbiert und nur das angewendet, was sie von C++ wussten. Im Gegensatz dazu hat in Objective-C Apple den Zuordnungsschritt vom Initialisierungsschritt getrennt, sodass Konstruktoren in dieser Sprache Ausnahmen werfen.

C # kann keinen Speicher von einem erfolglosen Aufruf an einen Konstruktor verlieren. Sogar einige Klassen im .NET Framework lösen Ausnahmen in ihren Konstruktoren aus.

23
Ant

Auslösen einer Ausnahme IFF Die Klasse kann hinsichtlich ihrer semantischen Verwendung nicht in einen konsistenten Zustand versetzt werden. Sonst nicht. Lassen Sie NIEMALS zu, dass ein Objekt in einem inkonsistenten Zustand existiert. Dies beinhaltet, dass keine vollständigen Konstruktoren bereitgestellt werden (z. B. ein leerer Konstruktor + initialize (), bevor Ihr Objekt tatsächlich vollständig erstellt wurde) ... SAGEN SIE NUR NEIN!

Zur Not macht es jeder. Ich habe es neulich für ein sehr eng genutztes Objekt in einem engen Rahmen gemacht. Eines Tages werde ich oder jemand anderes wahrscheinlich den Preis für diesen Beleg in der Praxis bezahlen.

Ich sollte beachten, dass mit "Konstruktor" das gemeint ist, was der Client aufruft, um das Objekt zu erstellen. Das könnte genauso gut etwas anderes sein als ein tatsächliches Konstrukt, das den Namen "Konstruktor" trägt. Zum Beispiel würde so etwas in C++ nicht gegen das Prinzip IMNSHO verstoßen:

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

Da der einzige Weg, um ein funky_object ist durch Aufrufen von build_funky Das Prinzip, niemals zuzulassen, dass ein ungültiges Objekt existiert, bleibt erhalten, obwohl der eigentliche "Konstruktor" den Job nicht beendet.

Das ist jedoch eine Menge zusätzlicher Arbeit für fragwürdigen Gewinn (vielleicht sogar Verlust). Ich würde immer noch die Ausnahmeroute bevorzugen.

13
Edward Strange

In diesem Fall würde ich die Factory-Methode verwenden. Grundsätzlich sollten Sie festlegen, dass Ihre Klasse nur private Konstruktoren und eine Factory-Methode hat, die eine Instanz Ihres Objekts zurückgibt. Wenn die Anfangsparameter ungültig sind, geben Sie einfach null zurück und lassen Sie den aufrufenden Code entscheiden, was zu tun ist.

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

Wirf niemals Ausnahmen, es sei denn, die Bedingungen sind außergewöhnlich. Ich denke, dass in diesem Fall ein leerer Wert leicht übergeben werden kann. In diesem Fall würden durch die Verwendung dieses Musters Ausnahmen und die damit verbundenen Leistungseinbußen vermieden, während der MyClass-Status gültig bleibt.

9
Donn Relacion
  • Ein Konstruktor sollte keine Nebenwirkungen haben.
    • Alles andere als die Initialisierung eines privaten Feldes sollte als Nebeneffekt angesehen werden.
    • Ein Konstruktor mit Nebenwirkungen bricht das Single-Responsibilty-Prinzip (SRP) und widerspricht dem Geist der objektorientierten Programmierung (OOP).
  • Ein Konstruktor sollte leicht sein und niemals versagen.
    • Zum Beispiel schaudere ich immer, wenn ich einen Try-Catch-Block in einem Konstruktor sehe. Ein Konstruktor sollte keine Ausnahmen oder Protokollfehler auslösen.

Man könnte diese Richtlinien vernünftigerweise in Frage stellen und sagen: "Aber ich befolge diese Regeln nicht und mein Code funktioniert gut!" Darauf würde ich antworten: "Nun, das mag wahr sein, bis es nicht mehr so ​​ist."

  • Ausnahmen und Fehler innerhalb eines Konstruktors sind sehr unerwartet. Wenn sie nicht dazu aufgefordert werden, werden zukünftige Programmierer nicht geneigt sein, diese Konstruktoraufrufe mit defensivem Code zu umgeben.
  • Wenn in der Produktion etwas fehlschlägt, ist es möglicherweise schwierig, die generierte Stapelverfolgung zu analysieren. Der obere Rand des Stack-Trace zeigt möglicherweise auf den Konstruktoraufruf, aber im Konstruktor passieren viele Dinge, und er zeigt möglicherweise nicht auf den tatsächlichen LOC, der fehlgeschlagen ist.
    • Ich habe viele .NET-Stack-Traces analysiert, wo dies der Fall war.
2
Jim G.

Ich bevorzuge es, einen Standard festzulegen, aber ich weiß, dass Java hat die "Apache Commons" -Bibliothek, die so etwas macht, und ich denke, das ist auch eine ziemlich gute Idee. Ich verstehe nicht Ein Problem beim Auslösen einer Ausnahme, wenn der ungültige Wert das Objekt in einen unbrauchbaren Zustand versetzen würde. Eine Zeichenfolge ist kein gutes Beispiel, aber was ist, wenn es sich um die DI eines armen Mannes handelt? Sie könnten nicht operieren, wenn ein Wert von null wurde anstelle einer ICustomerRepository -Schnittstelle übergeben. In einer solchen Situation ist das Auslösen einer Ausnahme die richtige Vorgehensweise, würde ich sagen.

0
Wayne Molina

Es hängt davon ab, was MyClass tut. Wenn MyClass tatsächlich eine Datenrepository-Klasse und Parametertext eine Verbindungszeichenfolge wäre, wäre es empfehlenswert, eine ArgumentException auszulösen. Wenn MyClass jedoch (zum Beispiel) die StringBuilder-Klasse ist, können Sie sie einfach leer lassen.

Es kommt also darauf an, wie wichtig Parametertext für die Methode ist - macht das Objekt mit einem Null- oder Leerwert Sinn?

0
Steven Striga