Warum ist der folgende C # -Code nicht zulässig:
public abstract class BaseClass
{
public abstract int Bar { get;}
}
public class ConcreteClass : BaseClass
{
public override int Bar
{
get { return 0; }
set {}
}
}
CS0546 'ConcreteClass.Bar.set': Kann nicht überschrieben werden, da 'BaseClass.Bar' keinen überschreibbaren Set-Accessor hat
c#
.net
properties
getter-setter
Ripper234
quelle
quelle
Foo
die nur eine geschützte abstrakteGetFoo
Methode umschließt. Ein abstrakter abgeleiteter Typ kann die Eigenschaft mit einer nicht virtuellen Lese- / Schreibeigenschaft beschatten, die nichts anderes tut, als die oben genannteGetFoo
Methode zusammen mit einer abstraktenSetFoo
Methode zu verpacken . Ein konkreter abgeleiteter Typ könnte das oben Genannte tun, aber Definitionen fürGetFoo
und liefernSetFoo
.Antworten:
Weil der Autor von Baseclass ausdrücklich erklärt hat, dass Bar eine schreibgeschützte Eigenschaft sein muss. Für Ableitungen ist es nicht sinnvoll, diesen Vertrag zu brechen und ihn schreibgeschützt zu machen.
Ich bin mit Microsoft in diesem Fall.
Angenommen, ich bin ein neuer Programmierer, dem gesagt wurde, er solle gegen die Baseclass-Ableitung codieren. Ich schreibe etwas, das davon ausgeht, dass Bar nicht beschrieben werden kann (da die Basisklasse ausdrücklich angibt, dass es sich um eine Get-Only-Eigenschaft handelt). Jetzt mit Ihrer Ableitung kann mein Code brechen. z.B
Da Bar nicht gemäß der BaseClass-Schnittstelle festgelegt werden kann, geht BarProvider davon aus, dass das Caching eine sichere Sache ist - da Bar nicht geändert werden kann. Wenn set in einer Ableitung möglich war, kann diese Klasse veraltete Werte liefern, wenn jemand die Bar-Eigenschaft des _source-Objekts extern ändert. Der Punkt ist " Sei offen, vermeide hinterhältige Dinge und überrasche Leute ".
Update : Ilya Ryzhenkov fragt: "Warum spielen Schnittstellen dann nicht nach denselben Regeln?" Hmm .. das wird schlammiger, wenn ich darüber nachdenke.
Eine Schnittstelle ist ein Vertrag, der besagt, dass eine Implementierung eine Leseeigenschaft namens Bar haben soll. Persönlich ist es viel weniger wahrscheinlich, dass ich diese Annahme als schreibgeschützt empfinde, wenn ich eine Schnittstelle sehe. Wenn ich eine get-only-Eigenschaft auf einer Schnittstelle sehe, lese ich sie als 'Jede Implementierung würde dieses Attribut Bar verfügbar machen' ... in einer Basisklasse klickt sie als 'Bar ist eine schreibgeschützte Eigenschaft'. Natürlich brechen Sie technisch gesehen nicht den Vertrag. Sie tun mehr. Sie haben also in gewissem Sinne Recht. Abschließend möchte ich sagen: "Machen Sie es so schwer wie möglich, dass Missverständnisse auftauchen."
quelle
InvalidOperationException("This instance is read-only")
.Ich denke, der Hauptgrund ist einfach, dass die Syntax zu explizit ist, als dass dies anders funktionieren könnte. Dieser Code:
ist ziemlich explizit, dass sowohl das
get
als auch dasset
überschrieben werden. Da esset
in der Basisklasse keine gibt , beschwert sich der Compiler. So wie Sie eine Methode, die nicht in der Basisklasse definiert ist, nicht überschreiben können, können Sie auch einen Setter nicht überschreiben.Sie könnten sagen, dass der Compiler Ihre Absicht erraten und die Überschreibung nur auf die Methode anwenden sollte, die überschrieben werden kann (dh in diesem Fall der Getter), aber dies widerspricht einem der C # -Designprinzipien - dass der Compiler Ihre Absichten nicht erraten darf , weil es falsch raten kann, ohne dass Sie es wissen.
Ich denke, die folgende Syntax mag gut funktionieren, aber wie Eric Lippert immer wieder sagt, ist die Implementierung selbst einer kleinen Funktion wie dieser immer noch ein großer Aufwand ...
oder für automatisch implementierte Eigenschaften
quelle
Ich bin heute auf das gleiche Problem gestoßen und ich denke, ich habe einen sehr triftigen Grund, dies zu wollen.
Zunächst möchte ich argumentieren, dass eine Get-Only-Eigenschaft nicht unbedingt schreibgeschützt ist. Ich interpretiere es als "Von dieser Schnittstelle / Zusammenfassung können Sie diesen Wert erhalten", was nicht bedeutet, dass eine Implementierung dieser Schnittstelle / abstrakten Klasse den Benutzer / das Programm nicht benötigt, um diesen Wert explizit festzulegen. Abstrakte Klassen dienen dazu, einen Teil der benötigten Funktionalität zu implementieren. Ich sehe absolut keinen Grund, warum eine geerbte Klasse keinen Setter hinzufügen konnte, ohne irgendwelche Verträge zu verletzen.
Das Folgende ist ein vereinfachtes Beispiel für das, was ich heute brauchte. Am Ende musste ich einen Setter in meine Benutzeroberfläche einfügen, um dies zu umgehen. Der Grund für das Hinzufügen des Setters und nicht das Hinzufügen einer SetProp-Methode besteht darin, dass eine bestimmte Implementierung der Schnittstelle DataContract / DataMember für die Serialisierung von Prop verwendet hat, was unnötig kompliziert gewesen wäre, wenn ich nur zu diesem Zweck eine andere Eigenschaft hinzufügen müsste der Serialisierung.
Ich weiß, dass dies nur ein "meine 2 Cent" -Post ist, aber ich finde das mit dem Originalplakat und der Versuch zu rationalisieren, dass dies eine gute Sache ist, erscheint mir seltsam, insbesondere wenn man bedenkt, dass die gleichen Einschränkungen nicht gelten, wenn direkt von einem geerbt wird Schnittstelle.
Auch die Erwähnung, new anstelle von override zu verwenden, trifft hier nicht zu, es funktioniert einfach nicht und selbst wenn es so wäre, würde es Ihnen nicht das gewünschte Ergebnis liefern, nämlich einen virtuellen Getter, wie von der Schnittstelle beschrieben.
quelle
Es ist möglich
tl; dr - Sie können eine Get-Only-Methode mit einem Setter überschreiben, wenn Sie möchten. Es ist im Grunde nur:
Erstellen Sie eine
new
Eigenschaft, die sowohl aget
als auch aset
mit demselben Namen hat.Wenn Sie nichts anderes tun, wird die alte
get
Methode weiterhin aufgerufen, wenn die abgeleitete Klasse über ihren Basistyp aufgerufen wird. Um dies zu beheben, fügen Sie eineabstract
Zwischenebene hinzu, dieoverride
die alteget
Methode verwendet, um sie zu zwingen,get
das Ergebnis der neuen Methode zurückzugeben.Dies ermöglicht es uns, Eigenschaften mit
get
/ zu überschreiben,set
selbst wenn sie in ihrer Basisdefinition keine hatten.Als Bonus können Sie bei Bedarf auch die Rückgabeart ändern.
Wenn die
get
Basisdefinition -only war, können Sie einen stärker abgeleiteten Rückgabetyp verwenden.Wenn die
set
Basisdefinition -only war, können Sie uns einen weniger abgeleiteten Rückgabetyp geben.Wenn die Basisdefinition bereits
get
/ warset
, dann:Sie können einen abgeleiteten Rückgabetyp verwenden, wenn Sie ihn nur
set
-on machen.Sie können einen weniger abgeleiteten Rückgabetyp verwenden, wenn Sie nur diesen
get
erstellen.In allen Fällen können Sie den gleichen Rückgabetyp beibehalten, wenn Sie möchten. Die folgenden Beispiele verwenden der Einfachheit halber denselben Rückgabetyp.
Situation:
get
Bestehendes EigentumSie haben eine Klassenstruktur, die Sie nicht ändern können. Vielleicht ist es nur eine Klasse oder es ist ein bereits vorhandener Vererbungsbaum. In jedem Fall möchten Sie
set
einer Eigenschaft eine Methode hinzufügen , können dies jedoch nicht.Problem: Kann nicht
override
dasget
-nur mitget
/set
Sie möchten
override
mit einerget
/set
Eigenschaft, aber es wird nicht kompiliert.Lösung: Verwenden Sie eine
abstract
ZwischenschichtSie können zwar nicht direkt
override
mit einerget
/set
-Eigenschaft arbeiten, aber Sie können :Erstellen Sie eine
new
get
/set
-Eigenschaft mit demselben Namen.override
die alteget
Methode mit einem Accessor für die neueget
Methode, um die Konsistenz sicherzustellen.Also schreiben Sie zuerst die
abstract
Zwischenschicht:Dann schreiben Sie die Klasse, die nicht früher kompiliert werden würde. Diesmal wird es kompiliert, da Sie nicht
override
dieget
Eigenschaft -only verwenden. Stattdessen ersetzen Sie es durch dasnew
Schlüsselwort.Ergebnis: Alles funktioniert!
Offensichtlich funktioniert dies wie vorgesehen
D
.Alles funktioniert immer noch wie beabsichtigt, wenn es
D
als eine seiner Basisklassen angezeigt wird, z . B.A
oderB
. Der Grund, warum es funktioniert, ist jedoch möglicherweise weniger offensichtlich.Die Definition der Basisklasse von
get
wird jedoch letztendlich immer noch von der Definition der abgeleiteten Klasse überschriebenget
, sodass sie immer noch vollständig konsistent ist.Diskussion
Mit dieser Methode können Sie -only-Eigenschaften
set
Methodenget
hinzufügen. Sie können es auch verwenden, um Dinge zu tun wie:Ändern Sie jede Eigenschaft in eine
get
-only-,set
-only- oderget
-and- -Eigenschaftset
, unabhängig davon, was sie in einer Basisklasse war.Ändern Sie den Rückgabetyp einer Methode in abgeleiteten Klassen.
Die Hauptnachteile sind, dass es mehr Codierung und ein zusätzliches
abstract class
im Vererbungsbaum gibt. Dies kann bei Konstruktoren, die Parameter verwenden, etwas ärgerlich sein, da diese in die Zwischenebene kopiert / eingefügt werden müssen.quelle
Ich bin damit einverstanden, dass es ein Anti-Muster ist, einen Getter in einem abgeleiteten Typ nicht überschreiben zu können. Schreibgeschützt gibt die mangelnde Implementierung an, nicht einen Vertrag mit einer reinen Funktion (impliziert durch die Top-Vote-Antwort).
Ich vermute, Microsoft hatte diese Einschränkung entweder, weil das gleiche Missverständnis gefördert wurde, oder weil die Grammatik vereinfacht wurde. Jetzt, da dieser Bereich angewendet werden kann, um individuell zu erhalten oder festzulegen, können wir vielleicht hoffen, dass dies auch beim Überschreiben möglich ist.
Das Missverständnis, das in der Top-Vote-Antwort angegeben ist, dass eine schreibgeschützte Eigenschaft irgendwie "reiner" sein sollte als eine Lese- / Schreibeigenschaft, ist lächerlich. Schauen Sie sich einfach viele gängige schreibgeschützte Eigenschaften im Framework an. der Wert ist keine Konstante / rein funktional; Zum Beispiel ist DateTime.Now schreibgeschützt, aber alles andere als ein reiner Funktionswert. Der Versuch, einen Wert einer schreibgeschützten Eigenschaft zwischenzuspeichern, unter der Annahme, dass er beim nächsten Mal denselben Wert zurückgibt, ist riskant.
In jedem Fall habe ich eine der folgenden Strategien angewendet, um diese Einschränkung zu überwinden. beide sind weniger als perfekt, aber Sie können über diesen Sprachmangel hinaus humpeln:
quelle
Sie können das Problem möglicherweise umgehen, indem Sie eine neue Eigenschaft erstellen:
quelle
Ich kann alle Ihre Punkte verstehen, aber effektiv werden die automatischen Eigenschaften von C # 3.0 in diesem Fall unbrauchbar.
So etwas kann man nicht machen:
IMO, C # sollte solche Szenarien nicht einschränken. Es liegt in der Verantwortung des Entwicklers, es entsprechend zu verwenden.
quelle
Das Problem ist, dass Microsoft aus irgendeinem Grund entschieden hat, dass es drei verschiedene Arten von Eigenschaften geben sollte: schreibgeschützt, schreibgeschützt und schreibgeschützt, von denen in einem bestimmten Kontext möglicherweise nur eine mit einer bestimmten Signatur vorhanden ist. Eigenschaften dürfen nur von identisch deklarierten Eigenschaften überschrieben werden. Um das zu tun, was Sie wollen, müssen Sie zwei Eigenschaften mit demselben Namen und derselben Signatur erstellen - eine davon war schreibgeschützt und eine war schreibgeschützt.
Persönlich wünsche ich mir, dass das gesamte Konzept der "Eigenschaften" abgeschafft werden könnte, mit der Ausnahme, dass die eigenschaftsbezogene Syntax als syntaktischer Zucker verwendet werden könnte, um die Methoden "get" und "set" aufzurufen. Dies würde nicht nur die Option 'Set hinzufügen' erleichtern, sondern auch ermöglichen, dass 'get' einen anderen Typ als 'set' zurückgibt. Während eine solche Fähigkeit nicht sehr oft verwendet wird, kann es manchmal nützlich sein, wenn eine 'get'-Methode ein Wrapper-Objekt zurückgibt, während das' set 'entweder einen Wrapper oder tatsächliche Daten akzeptiert.
quelle
Hier ist eine Problemumgehung, um dies mithilfe von Reflection zu erreichen:
Auf diese Weise können wir einen Objektwert für eine Eigenschaft festlegen, die schreibgeschützt ist. Könnte aber nicht in allen Szenarien funktionieren!
quelle
Sie sollten Ihren Fragentitel dahingehend ändern, dass Ihre Frage ausschließlich das Überschreiben einer abstrakten Eigenschaft betrifft oder dass Ihre Frage das generelle Überschreiben der Get-Only-Eigenschaft einer Klasse betrifft.
Wenn erstere (eine abstrakte Eigenschaft überschreiben)
Dieser Code ist nutzlos. Eine Basisklasse allein sollte Ihnen nicht sagen, dass Sie gezwungen sind, eine Get-Only-Eigenschaft (möglicherweise eine Schnittstelle) zu überschreiben. Eine Basisklasse bietet allgemeine Funktionen, für die möglicherweise bestimmte Eingaben von einer implementierenden Klasse erforderlich sind. Daher kann die allgemeine Funktionalität abstrakte Eigenschaften oder Methoden aufrufen. In diesem Fall sollten Sie bei den allgemeinen Funktionsmethoden aufgefordert werden, eine abstrakte Methode zu überschreiben, z.
Aber wenn Sie keine Kontrolle darüber haben und die Funktionalität der Basisklasse von ihrer eigenen öffentlichen Eigenschaft liest (seltsam), dann tun Sie einfach Folgendes:
Ich möchte auf den (seltsamen) Kommentar hinweisen: Ich würde sagen, eine bewährte Methode besteht darin, dass eine Klasse nicht ihre eigenen öffentlichen Eigenschaften verwendet, sondern ihre privaten / geschützten Felder, wenn sie vorhanden sind. Das ist also ein besseres Muster:
Wenn letzteres (Überschreiben der Get-Only-Eigenschaft einer Klasse)
Jede nicht abstrakte Eigenschaft hat einen Setter. Andernfalls ist es nutzlos und Sie sollten es nicht verwenden. Microsoft muss Ihnen nicht erlauben, das zu tun, was Sie wollen. Grund dafür ist: Der Setter existiert in der einen oder anderen Form, und Sie können Veerryy leicht erreichen, was Sie wollen .
Die Basisklasse oder jede Klasse , wo Sie eine Eigenschaft mit lesen kann
{get;}
, hat SOME Art ausgesetzt Setter für diese Eigenschaft. Die Metadaten sehen folgendermaßen aus:Die Implementierung wird jedoch zwei Enden des Komplexitätsspektrums haben:
Am wenigsten komplex:
Am komplexesten:
Mein Punkt ist, wie auch immer, Sie haben den Setter ausgesetzt. In Ihrem Fall möchten Sie möglicherweise überschreiben,
int Bar
weil Sie nicht möchten, dass die Basisklasse damit umgeht, keinen Zugriff darauf haben, wie sie damit umgeht, oder weil Sie beauftragt wurden, gegen Ihren Willen schnell und unkompliziert Code zu haxen .Sowohl in letzterer als auch in früherer Form (Schlussfolgerung)
Lange Rede, kurzer Sinn: Microsoft muss nichts ändern. Sie können auswählen, wie Ihre implementierende Klasse eingerichtet werden soll, und ohne Konstruktor die gesamte oder keine Basisklasse verwenden.
quelle
Lösung für nur eine kleine Teilmenge von Anwendungsfällen, aber dennoch: In C # 6.0 wird automatisch ein "schreibgeschützter" Setter für überschriebene Nur-Getter-Eigenschaften hinzugefügt.
quelle
Denn das würde das Konzept der Kapselung und des Versteckens der Implementierung brechen. Betrachten Sie den Fall, wenn Sie eine Klasse erstellen, versenden und dann der Verbraucher Ihrer Klasse in der Lage ist, eine Eigenschaft festzulegen, für die Sie ursprünglich nur einen Getter bereitstellen. Es würde effektiv alle Invarianten Ihrer Klasse stören, auf die Sie sich bei Ihrer Implementierung verlassen können.
quelle
Das ist nicht unmöglich. Sie müssen lediglich das Schlüsselwort "new" in Ihrer Immobilie verwenden. Beispielsweise,
Es scheint, als ob dieser Ansatz beide Seiten dieser Diskussion zufriedenstellt. Die Verwendung von "new" unterbricht den Vertrag zwischen der Basisklassenimplementierung und der Unterklassenimplementierung. Dies ist erforderlich, wenn eine Klasse mehrere Verträge haben kann (entweder über die Schnittstelle oder die Basisklasse).
Hoffe das hilft
quelle
new
die virtuelle Basis nicht mehr überschreibt. Insbesondere wenn die Basiseigenschaft wie im ursprünglichen Beitrag abstrakt ist , ist es unmöglich, dasnew
Schlüsselwort in einer nicht abstrakten Unterklasse zu verwenden, da Sie alle abstrakten Mitglieder überschreiben müssen.Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)
Sie erhalten 5,Console.WriteLine(b.BaseProperty)
geben Ihnen 0, und wenn Ihr Nachfolger dies debuggen muss, sollten Sie sich weit, weit weg bewegen.Eine schreibgeschützte Eigenschaft in der Basisklasse gibt an, dass diese Eigenschaft einen Wert darstellt, der immer aus der Klasse heraus ermittelt werden kann (z. B. einen Aufzählungswert, der dem (db-) Kontext eines Objekts entspricht). Die Verantwortung für die Bestimmung des Werts bleibt also innerhalb der Klasse.
Das Hinzufügen eines Setters würde hier ein unangenehmes Problem verursachen: Ein Validierungsfehler sollte auftreten, wenn Sie den Wert auf einen anderen Wert als den einzelnen möglichen Wert setzen, den er bereits hat.
Regeln haben jedoch oft Ausnahmen. Es ist sehr gut möglich, dass beispielsweise in einer abgeleiteten Klasse der Kontext die möglichen Aufzählungswerte auf 3 von 10 einschränkt, der Benutzer dieses Objekts jedoch noch entscheiden muss, welche richtig ist. Die abgeleitete Klasse muss die Verantwortung für die Ermittlung des Werts an den Benutzer dieses Objekts delegieren. Es ist wichtig zu wissen , dass der Benutzer dieses Objekts diese Ausnahme gut kennen und die Verantwortung übernehmen sollte, den richtigen Wert festzulegen.
Meine Lösung in solchen Situationen wäre, die Eigenschaft schreibgeschützt zu lassen und der abgeleiteten Klasse eine neue Lese- / Schreibeigenschaft hinzuzufügen, um die Ausnahme zu unterstützen. Das Überschreiben der ursprünglichen Eigenschaft gibt einfach den Wert der neuen Eigenschaft zurück. Die neue Eigenschaft kann einen Eigennamen haben, der den Kontext dieser Ausnahme ordnungsgemäß angibt.
Dies unterstützt auch die gültige Bemerkung von Gishu: "Machen Sie es so schwer wie möglich, dass Missverständnisse auftauchen".
quelle
ReadableMatrix
Klasse (mit einer schreibgeschützten indizierten Eigenschaft mit zwei Argumenten) mit UntertypenImmutableMatrix
und würde keine Unbeholfenheit implizierenMutableMatrix
. Code, der nur eine Matrix lesen muss und sich nicht darum kümmert, ob er sich in Zukunft ändern könnte, könnte einen Parameter vom Typ akzeptierenReadableMatrix
und mit veränderlichen oder unveränderlichen Subtypen vollkommen zufrieden sein.List<T>.Count
Sie sich zum Beispiel auch an, was bedeutet: Es kann nicht zugewiesen werden, aber das bedeutet nicht, dass es "schreibgeschützt" ist, da es von Benutzern der Klasse nicht geändert werden kann. Es kann sehr gut, wenn auch indirekt, durch Hinzufügen und Entfernen von Listenelementen geändert werden.Denn auf IL-Ebene wird eine Lese- / Schreibeigenschaft in zwei Methoden (Getter und Setter) übersetzt.
Beim Überschreiben müssen Sie die zugrunde liegende Schnittstelle weiterhin unterstützen. Wenn Sie einen Setter hinzufügen könnten, würden Sie effektiv eine neue Methode hinzufügen, die für die Außenwelt in Bezug auf die Benutzeroberfläche Ihrer Klassen unsichtbar bleibt.
Das Hinzufügen einer neuen Methode würde zwar nicht die Kompatibilität per se beeinträchtigen, aber da dies verborgen bleiben würde, ist die Entscheidung, dies nicht zuzulassen, durchaus sinnvoll.
quelle
new
stattdessen verwendenoverride
.new
mit Ihren Ergänzungen verwenden.Weil eine Klasse mit einer schreibgeschützten Eigenschaft (kein Setter) wahrscheinlich einen guten Grund dafür hat. Beispielsweise ist möglicherweise kein Datenspeicher zugrunde. Wenn Sie einen Setter erstellen können, wird der von der Klasse festgelegte Vertrag gebrochen. Es ist nur schlecht, OOP.
quelle