"Konstante Korrektheit" in C #

79

Der Punkt der Konstantenkorrektheit besteht darin, eine Ansicht einer Instanz bereitstellen zu können, die vom Benutzer nicht geändert oder gelöscht werden kann. Der Compiler unterstützt dies, indem er darauf hinweist, wenn Sie die Konstanz innerhalb einer const-Funktion aufheben oder versuchen, eine Nicht-const-Funktion eines const-Objekts zu verwenden. Gibt es eine Methode, die ich in C # verwenden kann, ohne den const-Ansatz zu kopieren, die dieselben Ziele hat?

Ich bin mir der Unveränderlichkeit bewusst, aber das überträgt sich nicht wirklich auf Containerobjekte, um nur ein Beispiel zu nennen.

Tenpn
quelle
Es gibt eine nette Diskussion über mögliche Designs für Readonly unter http://blogs.msdn.com/brada/archive/2004/02/04/67859.aspx
Asaf R
Was ist dann mit der Implementierung const_castin C #?
Zerem

Antworten:

64

Ich bin auch oft auf dieses Problem gestoßen und habe Schnittstellen verwendet.

Ich denke, es ist wichtig, die Idee fallen zu lassen, dass C # irgendeine Form oder sogar eine Weiterentwicklung von C ++ ist. Es sind zwei verschiedene Sprachen, die fast dieselbe Syntax haben.

Normalerweise drücke ich 'const-Korrektheit' in C # aus, indem ich eine schreibgeschützte Ansicht einer Klasse definiere:

public interface IReadOnlyCustomer
{
    String Name { get; }
    int Age { get; }
}

public class Customer : IReadOnlyCustomer
{
    private string m_name;
    private int m_age;

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        set { m_age = value; }
    }
}
Falle
quelle
12
Sicher, aber was ist, wenn eines Ihrer Felder eine Liste oder ein Rich-Typ ist? Ihre Lösung wird sehr schnell kompliziert.
Matt Cruikshank
12
@Trap: das ist der springende Punkt der Frage. Das Zurückgeben einer internen Sammlung ist eine schlechte Praxis, da die Außenwelt Ihre Interna in C ++ ändern kann, was durch die Verwendung von const gelöst wird: Sie bieten eine konstante Ansicht der Sammlung (können keine Elemente hinzufügen / entfernen, bieten nur eine konstante Ansicht ihrer Sammlung Elemente) und als solche ist es sicher (und eine gemeinsame Redewendung). Sie müssen keine Schnittstellen oder andere Tricks ausarbeiten, um zu verhindern, dass externer Code Ihre Interna ändert.
David Rodríguez - Dribeas
9
@Trap, ich stimme zu, dass der Mechanismus eine Entwicklungshilfe ist, die das Erkennen von Fehlern von erfahrenen oder nicht erfahrenen Entwicklern umfasst. Ich bin nicht der Meinung, dass das Schreiben von schreibgeschützten Schnittstellen aus mehreren Gründen eine bessere Lösung ist. Der erste Punkt ist, dass Sie beim Schreiben einer Klasse in C ++ implizit die konstante Schnittstelle definieren: die deklarierte Teilmenge der Mitglieder const, ohne die zusätzlichen Kosten für die Definition einer separaten Schnittstelle und die Notwendigkeit eines Laufzeitversands. Das heißt, die Sprache bietet eine einfache Möglichkeit, const-Schnittstellen zur Kompilierungszeit zu implementieren.
David Rodríguez - Dribeas
10
Es ist wichtig zu beachten , dass die const-ness ist Teil des Entwurfs und es erklärt die Absicht , ebenso klar wie eine externe const-Schnittstelle writting. Das Bereitstellen von const-Schnittstellen kann auch eine komplexe Aufgabe sein, wenn es manuell angegangen werden muss, und erfordert möglicherweise das Schreiben von fast so vielen Schnittstellen, wie Klassen im zusammengesetzten Typ vorhanden sind. Das bringt uns zu Ihrem letzten Satz: "Versauen Sie Dinge, weil Sie vergessen haben, die Konstante hinzuzufügen". Es ist einfacher, sich daran zu gewöhnen, das constÜberall hinzuzufügen (die Entwicklungskosten sind gering), als schreibgeschützte Schnittstellen für jede Klasse zu schreiben.
David Rodríguez - Dribeas
9
Einer der Gründe, warum ich C # wirklich mochte, als es herauskam, war, dass es nicht alle pragmatischen Funktionen von C ++ wie Java herausbrachte. Java hat versucht, alles mit Schnittstellen zu machen, und es war eine Katastrophe. Der Beweis ist im Pudding. Im Laufe der Zeit wurden viele Funktionen "zurück" zu Java hinzugefügt, die ursprünglich als Häresie denunziert wurden. Schnittstellenbasiertes Design hat seinen Platz, aber wenn es auf die Spitze getrieben wird, kann es die Architektur eines Programms unnötig komplizieren. Am Ende verbringen Sie mehr Zeit mit dem Schreiben von Boilerplate-Code und weniger Zeit damit, nützliche Dinge zu erledigen.
kgriffs
29

Um den Vorteil der Konstanz (oder Reinheit in Bezug auf die funktionale Programmierung) zu nutzen, müssen Sie Ihre Klassen so gestalten, dass sie unveränderlich sind, genau wie die String-Klasse von c #.

Dieser Ansatz ist weitaus besser, als nur ein Objekt als schreibgeschützt zu markieren, da Sie mit unveränderlichen Klassen Daten in Multitasking-Umgebungen problemlos weitergeben können.

Sam
quelle
8
Aber Unveränderlichkeit lässt sich nicht wirklich auf komplexe Objekte skalieren, oder?
Tenpn
8
Ich würde argumentieren, wenn Ihr Objekt so komplex wäre, dass Unveränderlichkeit unmöglich wäre, hätten Sie einen guten Kandidaten für das Refactoring.
Jim Burger
2
Ich denke, dieser ist der Beste der Gruppe. Unveränderliche Objekte werden zu selten verwendet.
3
Darüber hinaus gibt es Fälle, in denen Sie Objekte ändern möchten (Objekte ändern sich!), In den meisten Fällen jedoch eine schreibgeschützte Ansicht bieten. Unveränderliche Objekte bedeuten, dass Sie jedes Mal, wenn Sie eine Änderung vornehmen müssen (Änderung geschieht), ein neues Objekt mit denselben Daten außer der Änderung erstellen müssen. Stellen Sie sich eine Schule mit Schulräumen, Schülern vor. Möchten Sie jedes Mal eine neue Schule erstellen, wenn das Geburtsdatum eines Schülers vergeht und sich sein Alter ändert? Oder können Sie einfach das Alter auf Schülerebene ändern, den Schüler auf Raumebene, vielleicht den Raum auf Schulebene?
David Rodríguez - Dribeas
8
@tenpn: Unveränderlichkeit lässt sich unglaublich gut skalieren, wenn es richtig gemacht wird. Eine Sache, die wirklich hilft, ist die Verwendung von BuilderKlassen für große unveränderliche Typen (Java und .NET definieren die StringBuilderKlasse, die nur ein Beispiel ist).
Konrad Rudolph
24

Ich wollte Ihnen nur mitteilen, dass viele der System.Collections.Generics-Container über eine AsReadOnly-Methode verfügen, mit der Sie eine unveränderliche Sammlung zurückerhalten.

Rick Minerich
quelle
4
Dies hat immer noch das Problem, dass die Ts veränderbar sind, auch wenn die ReadOnlyCollection <T> dies nicht ist.
Arynaq
4

C # hat keine solche Funktion. Sie können Argumente als Wert oder als Referenz übergeben. Die Referenz selbst ist unveränderlich, es sei denn, Sie geben den Modifikator ref an . Referenzierte Daten sind jedoch nicht unveränderlich. Sie müssen also vorsichtig sein, wenn Sie Nebenwirkungen vermeiden möchten.

MSDN:

Parameter übergeben

aku
quelle
Nun, ich denke, darauf kommt es dann an: Was ist der beste Weg, um die Nebenwirkungen zu vermeiden, wenn man keine konstante Struktur hat?
Tenpn
Leider können nur unveränderliche Typen helfen. Sie können sich Spec # ansehen - es gibt einige interessante Überprüfungen der Kompilierungszeit.
Aku
3

Schnittstellen sind die Antwort und tatsächlich leistungsfähiger als "const" in C ++. const ist eine einheitliche Lösung für das Problem, bei dem "const" definiert ist als "setzt keine Mitglieder oder ruft etwas auf, das Mitglieder setzt". Das ist in vielen Szenarien eine gute Abkürzung für Konstanz, aber nicht in allen. Stellen Sie sich beispielsweise eine Funktion vor, die einen Wert basierend auf einigen Elementen berechnet, aber auch die Ergebnisse zwischenspeichert. In C ++ wird dies als Nicht-Konstante betrachtet, obwohl es aus Sicht des Benutzers im Wesentlichen Konstante ist.

Schnittstellen bieten Ihnen mehr Flexibilität bei der Definition der spezifischen Teilmenge der Funktionen, die Sie von Ihrer Klasse bereitstellen möchten. Willst du Konstanz? Stellen Sie einfach eine Schnittstelle ohne Mutationsmethoden bereit. Möchten Sie zulassen, dass einige Dinge eingestellt werden, andere jedoch nicht? Stellen Sie eine Schnittstelle mit genau diesen Methoden bereit.

großartig
quelle
15
Nicht 100% korrekt. C ++ const-Methoden dürfen Mitglieder mutieren, die als gekennzeichnet sind mutable.
Constantin
3
Meinetwegen. Und mit Const-Casting können Sie Konstanz loswerden. Beides impliziert, dass sogar die C ++ - Designer erkannt haben, dass One-Size-Fits-All wirklich One-Size-Fits-Most ist.
Munificent
8
Der Vorteil von C ++ ist, dass Sie in den meisten Fällen const haben und Schnittstellen für andere implementieren können. Neben const verfügt C ++ nun über das veränderbare Schlüsselwort, das auf Attribute als Caches von Daten oder Sperrmechanismen (Mutexe oder dergleichen) angewendet werden kann. Const ist nicht 'wird kein internes Attribut ändern), sondern wird den von außen wahrgenommenen Objektzustand nicht ändern. Das heißt, jede Verwendung des Objekts vor und nach dem Aufruf einer const-Methode führt zum gleichen Ergebnis.
David Rodríguez - Dribeas
9
Das Wegwerfen von const, um etwas in C ++ zu mutieren, ist ein unfreundliches Verhalten (Nasendämonen). const_cast dient zur Schnittstelle mit Legacy-Code, der, obwohl er logisch ist, nicht als const markiert ist.
jk.
3

Stimmen Sie einigen anderen zu, indem Sie schreibgeschützte Felder verwenden, die Sie im Konstruktor initialisieren, um unveränderliche Objekte zu erstellen.

    public class Customer
    {
    private readonly string m_name;
    private readonly int m_age;

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
    }

    public int Age
    {
        get { return m_age; }
    }
  }

Alternativ können Sie auch Zugriffsbereich für die Eigenschaften hinzufügen, z. B. public get und protected set?

    public class Customer
    {
    private string m_name;
    private int m_age;

    protected Customer() 
    {}

    public Customer(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    public string Name
    {
        get { return m_name; }
        protected set { m_name = value; }
    }

    public int Age
    {
        get { return m_age; }
        protected set { m_age = value; }
    }
  }
Stuart McConnell
quelle
3
Dies sind verschiedene Ansätze, die nicht wirklich zum gesamten Problem passen. Mit der Zugriffskontrollebene können Sie nur Klassen aus Änderungen ableiten. In vielen Fällen wird die reale Welt dadurch nicht angemessen modelliert. Ein Lehrer leitet sich nicht aus einem Schülerdatensatz ab, sondern möchte möglicherweise die Noten der Schüler ändern, obwohl der Schüler die Noten nicht ändern kann, sondern sie lesen kann ... um nur ein einfaches Beispiel zu nennen.
David Rodríguez - Dribeas
2
  • Die const Schlüsselwort kann zum Kompilieren von Zeitkonstanten wie primitiven Typen und Zeichenfolgen verwendet werden
  • Das Schlüsselwort readonly kann für Laufzeitkonstanten wie Referenztypen verwendet werden

Das Problem mit readonly ist, dass nur die Referenz (Zeiger) konstant sein kann. Das Objekt, auf das verwiesen wird (auf das verwiesen wird), kann noch geändert werden. Dies ist der schwierige Teil, aber es führt kein Weg daran vorbei. Um konstante Objekte zu implementieren, müssen sie keine veränderlichen Methoden oder Eigenschaften verfügbar machen. Dies ist jedoch umständlich.

Siehe auch Effektives C #: 50 spezifische Möglichkeiten zur Verbesserung Ihres C # (Punkt 2 - Lesen Sie lieber nur const.)

Thomas Bratt
quelle