So implementieren Sie eine schreibgeschützte Eigenschaft

78

Ich muss eine schreibgeschützte Eigenschaft für meinen Typ implementieren . Darüber hinaus wird der Wert dieser Eigenschaft im Konstruktor festgelegt und nicht geändert (ich schreibe eine Klasse, die benutzerdefinierte geroutete UI-Befehle für WPF verfügbar macht, aber das spielt keine Rolle).

Ich sehe zwei Möglichkeiten, dies zu tun:

  1. class MyClass
    {
        public readonly object MyProperty = new object();
    }
    
  2. class MyClass
    {
        private readonly object my_property = new object();
        public object MyProperty { get { return my_property; } }
    }
    

Mit all diesen FxCop-Fehlern, die besagen, dass ich keine öffentlichen Mitgliedsvariablen haben sollte, scheint es, dass die zweite der richtige Weg ist, dies zu tun. Richtig?

Gibt es in diesem Fall einen Unterschied zwischen einer get only-Eigenschaft und einem schreibgeschützten Mitglied?

Ich würde mich über Kommentare / Ratschläge / etc.

akonsu
quelle
30
Ich wünsche mir manchmal, dass die Syntax der automatischen Eigenschaften eine get; readonly set;Option enthält.
Dan Bryant
@ DanBryant Zumindest gibt es get; private set;.
März 2377
2
@ Marc.2377, Eigentlich haben sie vor {get;}kurzem Unterstützung hinzugefügt , wodurch dieses Problem behoben wird .
Dan Bryant
@ DanBryant Ah, in der Tat. Ich hätte zuerst die Antworten lesen sollen;)
März 2377

Antworten:

50

Versionierung:
Ich denke, es macht keinen großen Unterschied, wenn Sie nur an der Quellkompatibilität interessiert sind.
Die Verwendung einer Eigenschaft ist aus Gründen der Binärkompatibilität besser, da Sie sie durch eine Eigenschaft mit einem Setter ersetzen können, ohne den kompilierten Code abhängig von Ihrer Bibliothek zu beschädigen.

Konvention:
Sie folgen der Konvention. In solchen Fällen, in denen die Unterschiede zwischen den beiden Möglichkeiten nach der Konvention relativ gering sind, ist dies besser. Ein Fall, in dem es zurückkommen könnte, um Sie zu beißen, ist reflexionsbasierter Code. Möglicherweise werden nur Eigenschaften und keine Felder akzeptiert, z. B. ein Eigenschafteneditor / -betrachter.

Serialisierung
Wenn Sie von Feld zu Eigenschaft wechseln, werden wahrscheinlich viele Serialisierer beschädigt. Und AFAIK XmlSerializerserialisiert nur öffentliche Objekte und keine öffentlichen Felder.

Verwenden einer Autoproperty
Eine weitere häufige Variante ist die Verwendung einer Autoproperty mit einem privaten Setter. Dies ist zwar kurz und eine Eigenschaft, erzwingt jedoch nicht die Lesbarkeit. Also bevorzuge ich die anderen.

Das schreibgeschützte Feld ist selbstdokumentierend. Das Feld hat
jedoch einen Vorteil:
Auf einen Blick auf die öffentliche Schnittstelle wird deutlich, dass es tatsächlich unveränderlich ist (außer bei Reflexion). Während Sie bei einer Eigenschaft nur sehen können, dass Sie sie nicht ändern können, müssen Sie sich auf die Dokumentation oder Implementierung beziehen.

Aber um ehrlich zu sein, verwende ich den ersten ziemlich oft im Anwendungscode, da ich faul bin. In Bibliotheken bin ich normalerweise gründlicher und folge der Konvention.

C # 6.0 fügt schreibgeschützte automatische Eigenschaften hinzu

public object MyProperty { get; }

Wenn Sie also keine älteren Compiler unterstützen müssen, können Sie eine wirklich schreibgeschützte Eigenschaft mit Code haben, der genauso präzise ist wie ein schreibgeschütztes Feld.

CodesInChaos
quelle
1
Im Moment public type prop => get;
wünsche
65

Der zweite Weg ist die bevorzugte Option.

private readonly int MyVal = 5;

public int MyProp { get { return MyVal;}  }

Dadurch wird sichergestellt, dass MyValdies nur bei der Initialisierung zugewiesen werden kann (es kann auch in einem Konstruktor festgelegt werden).

Wie Sie bereits bemerkt haben, stellen Sie auf diese Weise kein internes Mitglied zur Verfügung, sodass Sie die interne Implementierung in Zukunft ändern können.

Oded
quelle
Vielen Dank. Die erste Option sorgt auch dafür. Was ist Ihrer Meinung nach der Grund, warum dies die bevorzugte Option ist?
Akonsu
Gibt es einen Grund, den wir nicht verwenden sollten public int MyProp { get; private set; }? Ich weiß, dass es nicht wirklich schreibgeschützt ist, aber es ist verdammt nah dran.
Mike Hofer
3
@ Mike Hofer - Da das int als schreibgeschützt deklariert ist , können Sie es nicht außerhalb eines Konstruktors ändern.
Oded
5
@ Mike Hofer - es kommt wirklich darauf an, was die Absicht ist ... Die Version, die ich geschrieben habe, macht ein internes Mitglied verfügbar, dessen Wert sich nach der Initialisierung nicht ändern kann. Ihr macht ein Mitglied verfügbar, dessen Wert sich nach der Initialisierung ändern kann. Kommt wirklich darauf an, was du willst ... Meins ist schreibgeschützt, wie in, kann nach init überhaupt nicht mehr geändert werden, deines ist schreibgeschützt, wie in, von jeder externen Klasse.
Oded
1
@Oded - das kann ich akzeptieren. Es ist ein subtiler, aber wichtiger Unterschied. Ich kann sehen, wo es nützlich wäre, wenn Sie eine Eigenschaft verfügbar machen möchten, die sich sowohl intern als auch extern als Konstante verhält. Meins würde das bestimmt nicht tun.
Mike Hofer
48

Mit der Einführung von C # 6 (in VS 2015) können Sie jetzt nur getnoch automatische Eigenschaften haben, in denen sich das implizite Sicherungsfeld befindet readonly(dh Werte können im Konstruktor zugewiesen werden, aber nicht anderswo):

public string Name { get; }

public Customer(string name)  // Constructor
{
    Name = name;
}

private void SomeFunction()
{
    Name = "Something Else";  // Compile-time error
}

Und Sie können jetzt auch Eigenschaften (mit oder ohne Setter) inline initialisieren:

public string Name { get; } = "Boris";

Wenn Sie auf die Frage zurückkommen, erhalten Sie die Vorteile von Option 2 (öffentliches Mitglied ist eine Eigenschaft, kein Feld) mit der Kürze von Option 1.

Leider bietet es keine Garantie für Unveränderlichkeit auf der Ebene der öffentlichen Schnittstelle (wie in @ CodesInChaos 'Punkt zur Selbstdokumentation), da für einen Verbraucher der Klasse kein Setter von einem privaten Setter nicht zu unterscheiden ist.

Bob Sammers
quelle
Gute Informationen zur neuen Syntax Sie können jedoch einen privaten Setter reflektieren und aktivieren und / oder einen Wert in ein Hintergrundfeld laden (unabhängig von Zugriffsmodifikatoren). Es ist auch möglich, zwischen einem privaten Setter und dem Fehlen eines Setzers beim Ausführen zu unterscheiden -Zeit mit Reflexion.
Shaun Wilson
1
@Shaun: guter Punkt - es gibt viele Dinge, die Sie mit Reflexion tun können! Einige der Möglichkeiten widersprechen wahrscheinlich der Absicht des ursprünglichen Programmierers oder sogar der Sprachdesigner, aber sagen Sie, dass readonlyFelder durch Reflexion geändert werden können (ich kenne die Antwort nicht, aber es scheint problematisch)?
Bob Sammers
2
Ich habe das nicht besonders gesagt, nein, aber die Antwort lautet: Ja ... du kannst. gist.github.com/wilson0x4d/a053c0fd57892d357b2c Wenn Sie der Meinung sind, dass dies problematisch ist, warten Sie einfach, bis Sie erfahren, dass jedes Betriebssystem auf dem Planeten über einen Mechanismus verfügt, mit dem ein Prozess den Speicher eines anderen Prozesses lesen / schreiben kann (bei ausreichenden Ausführungsberechtigungen). das heißt.) Deshalb kann kein softwarebasiertes System jemals wirklich sicher sein, aber ich schweife ab, das hat nicht viel mit der ursprünglichen Frage zu tun :) auch wenn es interessant ist!
Shaun Wilson
Ich würde empfehlen, den ausführlichen Artikel sowie den Artikel von
BillWagner zu
12

Du kannst das:

public int Property { get { ... } private set { ... } }
Niemand
quelle
8
Ja, das können Sie, aber mit dieser Technik garantieren Sie nur, dass die Eigenschaft von Verbrauchern der Klasse nicht geändert werden kann und nicht, dass sie für die Lebensdauer des Objekts konstant bleibt.
Bob Sammers
Bob: Wenn die Eigenschaft von den Verbrauchern der Klasse nicht geändert werden kann, lautet die Eigenschaft "readOnly", nicht wahr?
El Bayames
@ElBayames Sie können die Referenz nicht ändern, aber Sie können die Interna ändern. Überlegen Sie, wann Sie ein Objekt mit internem Status verfügbar machen. Sie können das Objekt einfach mit der exponierten get-Methode abrufen und dann die internen Eigenschaften dieses Objekts ändern. Das ist kein wahrer Readonly.
Matthew S
5

Ich stimme zu, dass der zweite Weg vorzuziehen ist. Der einzige wirkliche Grund für diese Einstellung ist die allgemeine Einstellung, dass .NET-Klassen keine öffentlichen Felder haben. Wenn dieses Feld jedoch schreibgeschützt ist, kann ich nicht erkennen, wie es zu echten Einwänden kommen würde, außer einer mangelnden Konsistenz mit anderen Eigenschaften. Der wirkliche Unterschied zwischen einem schreibgeschützten Feld und einer Nur-Erhalten-Eigenschaft besteht darin, dass das Nur-Lesen-Feld eine Garantie dafür bietet, dass sich sein Wert während der Lebensdauer des Objekts nicht ändert und eine Nur-Erhalten-Eigenschaft nicht.

Eric Mickelsen
quelle
4

Das zweite Verfahren ist wegen der Einkapselung bevorzugt. Sie können sicher sein, dass das schreibgeschützte Feld öffentlich ist, aber das widerspricht C # -Sprachen, bei denen der Datenzugriff über Eigenschaften und nicht über Felder erfolgt.

Der Grund dafür ist, dass die Eigenschaft eine öffentliche Schnittstelle definiert. Wenn sich die Backing-Implementierung dieser Eigenschaft ändert, wird der Rest des Codes nicht beschädigt, da die Implementierung hinter einer Schnittstelle verborgen ist.

Joshua Rodgers
quelle
1

In C # 9 wird Microsoft eine neue Methode einführen, mit der Eigenschaften nur bei der Initialisierung mithilfe der folgenden init;Methode festgelegt werden:

public class Person
{
  public string firstName { get; init; }
  public string lastName { get; init; }
}

Auf diese Weise können Sie beim Initialisieren eines neuen Objekts Werte zuweisen:

var person = new Person
{
  firstname = "John",
  lastName = "Doe"
}

Aber später können Sie es nicht ändern:

person.lastName = "Denver"; // throws a compiler error
Mekroebo
quelle