Const Correctnes nachbilden
-
Hi,
die letzten Jahre habe ich nur in C++ entwickelt und bin jetzt fuer ein Projekt zu C# gewechselt. Vieles wird in C# ja anders geloest und ich kann mich mit allem anfreunden. Das einzige was mich etwas stoert ist die fehlende "const correctnes". Bei Stackoverflow und in diversen anderen Foren gibt es ja einige Threads darueber. Ich sehe momentan zwei Moeglichkeiten. Ersten koennte ich es ignoerieren, da man es in C# einfach anders handhabt. Die zweite Moeglichkeit besteht darin, dass ich das Verhalten durch Interfaces nachbilde. Siehe das folgende Beispiel. An sich bin ich damit auch ganz zufrieden. Ich frage mich nun, ob das in der C# als volkommen exotisch angesehen wird und ich damit gegen jegliche Konvention verstosse. Hat damit jemand schon Erfahrung gemacht?
interface IFooReadOnly { int Property { get; } } class Foo { public int Property { get; set; } } class Bar { List<Foo> privateData_; public IReadOnlyCollection<IFooReadOnly> privateDataReadOnly() { return privateData_.Cast<IFooReadOnly>().ToList().AsReadOnly(); } }
-
Es muss natuerlich:
class Foo : IFooReadOnly { public int Property { get; set; } }
heissen.
-
Die BCL enthält mittlerweile einige "immutable view"-Interfaces, etwa
IEnumerable<>
,IReadOnlyCollection<>
,IReadOnlyList<>
. Deren Verwendung ist also durchaus idiomatisch. Allerdings machst du es viel zu kompliziert:public IReadOnlyCollection<IFooReadOnly> privateDataReadOnly() { return privateData_.Cast<IFooReadOnly>().ToList().AsReadOnly(); }
Der
Cast<>()
-Aufruf ist unnötig, weil das Argument vonIReadOnlyCollection<>
als kovariant deklariert ist, d.h. es gibt eine implizite Umwandlung vonIReadOnlyCollection<B>
nachIReadOnlyCollection<A>
, wennB
vonA
erbt.Ohne
Cast<>()
wird auch dasToList()
unnötig. Allerdings ändert sich dadurch die Semantik der Funktion: wegen desToList()
-Aufrufs gibt obigesprivateDataReadOnly()
einen "Snapshot" der privaten Daten zurück, d.h. wenn ichvar data = privateDataReadOnly();
mache und dann einen Mutator vonBar
aufrufe, derprivateData_
ändert, sehe ich die Änderungen indata
nicht.AsReadOnly()
brauchst du auch nicht aufrufen, weilList<>
auchIReadOnlyList<>
implementiert. Du kannst es verwenden, wenn du verhindern willst, daß ein Aufrufer den Rückgabewert vonprivateDataReadOnly()
nachList<>
castet und dann in deinen privaten Daten rumschreibt. Dazu gehen die Meinungen auseinander; ich finde, das ist auch nichts anderes als einconst_cast<>()
in C++, und wer das macht, ist selbst schuld; aber je nach "Audienz" gibt es auch gute gegenteilige Argumente. Wenn es dir jedenfalls reicht, daß der statische Typ keine Modifikationen mehr erlaubt, kannst du die Funktion so implementieren:public IReadOnlyCollection<IFooReadOnly> privateDataReadOnly() => privateData_; // oder privateData_.AsReadOnly();
Und wenn wir schon dabei sind, das Ganze idiomatischer zu machen, dann sollte die öffentliche Memberfunktion PascalCase verwenden, und außerdem eher ein Property sein. Außerdem brauchen weder "ReadOnly" noch "private" Teil des Namens zu sein:
public IReadOnlyCollection<IFooReadOnly> Data => privateData_; // oder privateData_.AsReadOnly();
(Das mit dem Property gilt wohlgemerkt nur, wenn du nicht
ToList()
aufrufst. Meine intuitive Erwartung an Properties wäre, daß ihre Abfrage billig ist, also typischerweise O(1). MitToList()
eine Kopie der Liste anzulegen, ist O(N), das würde also m. E. in eine Funktion gehören.)
-
Wow, vielen Dank! Das werde ich mir mal alles zu Gemüte führen
-
Eine Lösung des "const Problems" ist auch Kopieren. Wird in C# und Java auch relativ oft gemacht soweit ich das beurteilen kann.
Ich hab' auch die Vermutung dass das einer der Gründe ist warum Micro-Services recht beliebt sind: An den Service-Grenzen ist man gezwungen* zu kopieren.
*: Gezwungen ist natürlich nicht ganz richtig. Theoretisch könnte man natürlich Proxies verwenden. Nur das tut man halt einfach nicht.