Ich habe eine Frage zu "Best Practices" zu OOP in C # (diese gilt jedoch für alle Sprachen).
Erwägen Sie, eine Bibliotheksklasse mit einem Objekt zu haben, das öffentlich zugänglich gemacht werden soll, z. B. über den Eigenschaften-Accessor. Wir möchten jedoch nicht, dass die Öffentlichkeit (Benutzer dieser Bibliotheksklasse) dies ändert.
class A
{
// Note: List is just example, I am interested in objects in general.
private List<string> _items = new List<string>() { "hello" }
public List<string> Items
{
get
{
// Option A (not read-only), can be modified from outside:
return _items;
// Option B (sort-of read-only):
return new List<string>( _items );
// Option C, must change return type to ReadOnlyCollection<string>
return new ReadOnlyCollection<string>( _items );
}
}
}
Offensichtlich ist der beste Ansatz "Option C", aber nur sehr wenige Objekte haben die ReadOnly-Variante (und sicherlich keine benutzerdefinierten Klassen haben sie).
Wenn Sie der Benutzer der Klasse A wären, würden Sie Änderungen an erwarten
List<string> someList = ( new A() ).Items;
auf ursprüngliches (A) Objekt übertragen? Oder ist es in Ordnung, einen Klon zurückzugeben, sofern er in Kommentaren / Dokumentationen so geschrieben wurde? Ich denke, dass dieser Klonansatz zu ziemlich schwer zu verfolgenden Fehlern führen könnte.
Ich erinnere mich, dass wir in C ++ const-Objekte zurückgeben konnten und Sie nur Methoden aufrufen konnten, die als const darauf markiert waren. Ich denke, es gibt keine solche Funktion / Muster in der C #? Wenn nicht, warum würden sie es nicht aufnehmen? Ich glaube, es heißt konstante Korrektheit.
Andererseits ist meine Hauptfrage "Was würden Sie erwarten" oder wie man mit Option A gegen B umgeht.
quelle
Antworten:
Neben der Antwort von @ Jesse, die die beste Lösung für Sammlungen ist: Die allgemeine Idee besteht darin, Ihre öffentliche Schnittstelle von A so zu gestalten, dass sie nur zurückkehrt
Werttypen (wie int, double, struct)
unveränderliche Objekte (wie "Zeichenfolge" oder benutzerdefinierte Klassen, die nur schreibgeschützte Methoden anbieten, genau wie Ihre Klasse A)
(nur wenn Sie müssen): Objektkopien, wie Ihre "Option B" zeigt
IEnumerable<immutable_type_here>
ist an sich unveränderlich, daher ist dies nur ein Sonderfall der oben genannten.quelle
Beachten Sie auch, dass Sie auf Schnittstellen programmieren sollten. Im Großen und Ganzen möchten Sie von dieser Eigenschaft eine zurückgeben
IEnumerable<string>
. AList<string>
ist ein bisschen ein Nein-Nein, weil es im Allgemeinen veränderlich ist und ich werde so weit gehen zu sagen, dass esReadOnlyCollection<string>
als Implementierer vonICollection<string>
Gefühlen das Liskov-Substitutionsprinzip verletzt.quelle
IEnumerable<string>
ist genau das, was man hier will.IReadOnlyList<T>
.IList<T>
)Ich bin vor einiger Zeit auf ein ähnliches Problem gestoßen, und unten war meine Lösung, aber es funktioniert wirklich nur, wenn Sie auch die Bibliothek steuern:
Beginnen Sie mit Ihrer Klasse
A
Leiten Sie dann daraus eine Schnittstelle mit nur dem get-Teil der Eigenschaften (und allen Lesemethoden) ab und lassen Sie A diese implementieren
Wenn Sie dann die schreibgeschützte Version verwenden möchten, ist natürlich eine einfache Besetzung ausreichend. Dies ist genau das, was Ihre Lösung C ist, aber ich dachte, ich würde die Methode anbieten, mit der ich sie erreicht habe
quelle
Für alle, die diese Frage finden und dennoch an der Antwort interessiert sind - jetzt gibt es eine andere Option, die offen legt
ImmutableArray<T>
. Dies sind einige Punkte, warum ich denke, dass es besser ist alsIEnumerable<T>
oderReadOnlyCollection<T>
:IEnumerable<T>
tutIEnumerable
oderIReadOnlyCollect
nicht davon ausgehen kann, dass sie sich nie ändert (mit anderen Worten, es gibt keine Garantie dafür, dass Elemente einer Sammlung nicht geändert wurden, während der Verweis auf diese Sammlung unverändert bleibt).Auch wenn APi den Client über Änderungen in der Sammlung benachrichtigen muss, würde ich ein Ereignis als separates Mitglied verfügbar machen.
Beachten Sie, dass ich nicht sage, dass
IEnumerable<T>
das im Allgemeinen schlecht ist. Ich würde es immer noch verwenden, wenn ich die Sammlung als Argument übergebe oder eine träge initialisierte Sammlung zurückgebe (in diesem speziellen Fall ist es sinnvoll, die Anzahl der Elemente auszublenden, da dies noch nicht bekannt ist).quelle