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 von IReadOnlyCollection<> als kovariant deklariert ist, d.h. es gibt eine implizite Umwandlung von IReadOnlyCollection<B> nach IReadOnlyCollection<A> , wenn B von A erbt.

    Ohne Cast<>() wird auch das ToList() unnötig. Allerdings ändert sich dadurch die Semantik der Funktion: wegen des ToList() -Aufrufs gibt obiges privateDataReadOnly() einen "Snapshot" der privaten Daten zurück, d.h. wenn ich var data = privateDataReadOnly(); mache und dann einen Mutator von Bar aufrufe, der privateData_ ändert, sehe ich die Änderungen in data nicht.

    AsReadOnly() brauchst du auch nicht aufrufen, weil List<> auch IReadOnlyList<> implementiert. Du kannst es verwenden, wenn du verhindern willst, daß ein Aufrufer den Rückgabewert von privateDataReadOnly() nach List<> castet und dann in deinen privaten Daten rumschreibt. Dazu gehen die Meinungen auseinander; ich finde, das ist auch nichts anderes als ein const_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). Mit ToList() 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.


Log in to reply