it-swarm.com.de

Wie gehe ich mit Setzern auf unveränderlichen Feldern um?

Ich habe eine Klasse mit zwei readonly int Felder. Sie sind als Eigenschaften ausgesetzt:

public class Thing
{
    private readonly int _foo, _bar;

    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public int Foo { get { return _foo; } set { } }

    public int Bar { get { return _bar; } set { } }
}

Dies bedeutet jedoch, dass Folgendes vollkommen legal ist:

Thing iThoughtThisWasMutable = new Thing(1, 42);

iThoughtThisWasMutable.Foo = 99;  // <-- Poor, mistaken developer.
                                  //     He surely has bugs now. :-(

Die Fehler, die von ausgehen, wenn angenommen wird, dass dies funktionieren würde, sind mit Sicherheit heimtückisch. Sicher, der fehlerhafte Entwickler sollte die Dokumente gelesen haben. Das ändert aber nichts an der Tatsache, dass ihn kein Kompilierungs- oder Laufzeitfehler vor dem Problem gewarnt hat.

Wie sollte die Klasse Thing geändert werden, damit Entwickler den obigen Fehler weniger wahrscheinlich machen?

Eine Ausnahme werfen? Verwenden Sie eine Getter-Methode anstelle einer Eigenschaft?

25
kdbanman

Warum diesen Code legal machen?
Nehmen Sie die set { } wenn es nichts tut.
So definieren Sie eine schreibgeschützte öffentliche Eigenschaft:

public int Foo { get { return _foo; } }
106
paparazzo

Mit C # 5 und früher standen wir vor zwei Optionen für unveränderliche Felder, die über einen Getter freigelegt wurden:

  1. Erstellen Sie eine schreibgeschützte Sicherungsvariable und geben Sie diese über einen manuellen Getter zurück. Diese Option ist sicher (man muss das readonly explizit entfernen, um die Unveränderlichkeit zu zerstören. Es wurde jedoch viel Code für die Kesselplatte erstellt.
  2. Verwenden Sie eine Auto-Eigenschaft mit einem privaten Setter. Dies erzeugt einfacheren Code, aber es ist einfacher, die Unveränderlichkeit versehentlich zu brechen.

Mit C # 6 (verfügbar in VS2015, das gestern veröffentlicht wurde) erhalten wir jetzt das Beste aus beiden Welten: schreibgeschützte Auto-Eigenschaften. Dies ermöglicht es uns, die Klasse des OP zu vereinfachen, um:

public class Thing
{
    /// <summary> I AM IMMUTABLE. </summary>
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; }
    public int Bar { get; }
}

Das Foo = foo und Bar = bar Zeilen sind nur im Konstruktor gültig (der die robuste schreibgeschützte Anforderung erfüllt), und das Hintergrundfeld wird impliziert, anstatt explizit definiert werden zu müssen (wodurch der einfachere Code erreicht wird).

44
David Arno

Sie könnten einfach die Setter loswerden. Sie tun nichts, sie verwirren die Benutzer und führen zu Fehlern. Sie können sie jedoch stattdessen privat machen und so die Hintergrundvariablen entfernen, um Ihren Code zu vereinfachen:

public class Thing
{
    public Thing(int foo, int bar)
    {
        Foo = foo;
        Bar = bar;
    }

    public int Foo { get; private set; }

    public int Bar { get; private set; }
}
12
David Arno

Zwei Lösungen.

Einfach :

Schließen Sie keine Setter ein, wie von David für unveränderliche schreibgeschützte Objekte angegeben.

Alternativ :

Ermöglichen Sie den Setzern, ein neues unveränderliches Objekt zurückzugeben, das im Vergleich zum vorherigen ausführlich ist, aber für jedes initialisierte Objekt den zeitlichen Status bereitstellt. Dieses Design ist ein sehr nützliches Werkzeug für die Sicherheit und Unveränderlichkeit von Threads, das sich über alle zwingenden OOP Sprachen) erstreckt.

Pseudocode

public class Thing
{

{readonly vars}

    public Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}

öffentliche statische Fabrik

public class Thing
{

{readonly vars}

    private Thing(int foo, int bar)
    {
        _foo = foo;
        _bar = bar;
    }

    public static with(int foo, int bar) {
        return new Thing(foo, bar)
    }

    public Thing withFoo(int foo) {
        return new Thing(foo, getBar());
    }

    public Thing withBar(int bar) {
        return new Thing(getFoo(), bar)
    }

    etc...
}
6
Matt Smithies