Warum ist es unmöglich, eine Nur-Getter-Eigenschaft zu überschreiben und einen Setter hinzuzufügen? [geschlossen]


141

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


9
StackOverflow kann nur eingeschränkt Fragen im Auftrag von Microsoft beantworten. Überlegen Sie, Ihre Frage neu zu formulieren.
Jay Bazuzi

1
Ein einfaches Mittel gegen dieses störende Verhalten in .net ist übrigens eine nicht virtuelle schreibgeschützte Eigenschaft, Foodie nur eine geschützte abstrakte GetFooMethode umschließt. Ein abstrakter abgeleiteter Typ kann die Eigenschaft mit einer nicht virtuellen Lese- / Schreibeigenschaft beschatten, die nichts anderes tut, als die oben genannte GetFooMethode zusammen mit einer abstrakten SetFooMethode zu verpacken . Ein konkreter abgeleiteter Typ könnte das oben Genannte tun, aber Definitionen für GetFoound liefern SetFoo.
Supercat

1
Nur um dem Feuer Treibstoff hinzuzufügen, erlaubt C ++ / CLI dies.
Ymett

1
Die Möglichkeit, beim Überschreiben einer Eigenschaft einen Accessor hinzuzufügen, wurde als Änderung für eine zukünftige Version von C # vorgeschlagen ( github.com/dotnet/roslyn/issues/9482 ).
Brandon Bonds

1
Um ehrlich zu sein, wird diese Frage häufig gestellt, wenn ein vorhandener Bibliothekstyp erweitert werden muss, der das folgende Muster verwendet: schreibgeschützte Basisklasse, abgeleitete veränderbare Klasse. Da die Bibliothek bereits ein fehlerhaftes Muster verwendet (z. B. WPF), muss dieses fehlerhafte Muster umgangen werden. Die Vorlesung erspart einem nicht, am Ende des Tages eine Problemumgehung implementieren zu müssen.
Rwong

Antworten:


-4

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

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

  public void setSource(BaseClass b)
  {
    _source = b;
    _currentBar = b.Bar;
  }

  public Bar getBar()
  { return _currentBar;  }
}

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."


94
Ich bin noch nicht überzeugt. Selbst wenn es keinen expliziten Setter gibt, garantiert dies nicht, dass die Eigenschaft immer dasselbe Objekt zurückgibt - sie kann dennoch durch eine andere Methode geändert werden.
Ripper234

34
Sollte das dann nicht auch für Schnittstellen gelten? Sie können schreibgeschützte Eigenschaften für die Schnittstelle und Lese- / Schreibzugriff für den Implementierungstyp haben.
Ilya Ryzhenkov

49
Ich bin anderer Meinung: Die Basisklasse hat nur erklärt, dass die Eigenschaft keinen Setter hat. Dies ist nicht dasselbe wie die Eigenschaft, die schreibgeschützt ist. "Schreibgeschützt" ist nur eine Bedeutung, die Sie ihm zuweisen. In C # sind überhaupt keine relevanten Garantien eingebaut. Sie könnten eine get-only-Eigenschaft haben, die sich jedes Mal ändert, oder eine get / set-Eigenschaft, bei der der Setter eine auslöst InvalidOperationException("This instance is read-only").
Roman Starkov

21
Wenn ich Getter und Setter als Methoden betrachte, sehe ich nicht mehr, warum es eine "Vertragsverletzung" sein sollte, als eine neue Methode hinzuzufügen. Eine Basisklasse Vertrag hat nichts mit dem zu tun , was Funktionalität ist nicht vorhanden, nur mit dem, was ist vorhanden.
Dave Cousineau

37
-1 Ich bin mit dieser Antwort nicht einverstanden und finde sie falsch. Da eine Basisklasse das Verhalten definiert, an das sich jede Klasse halten muss (siehe Liskovs Substitutionsprinzip), das Hinzufügen von Verhalten jedoch nicht einschränkt (und nicht einschränken sollte). Eine Basisklasse kann nur definieren, wie das Verhalten sein muss, und sie kann (und sollte) nicht angeben, was das Verhalten "nicht sein darf" (dh "darf auf konkreten Ebenen nicht beschreibbar sein)".
Marcel Valdez Orozco

39

Ich denke, der Hauptgrund ist einfach, dass die Syntax zu explizit ist, als dass dies anders funktionieren könnte. Dieser Code:

public override int MyProperty { get { ... } set { ... } }

ist ziemlich explizit, dass sowohl das getals auch das setüberschrieben werden. Da es setin 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 ...

public int MyProperty
{
    override get { ... }
    set { ... }
}

oder für automatisch implementierte Eigenschaften

public int MyProperty { override get; set; }

19

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.

interface ITest
{
    // Other stuff
    string Prop { get; }
}

// Implements other stuff
abstract class ATest : ITest
{
    abstract public string Prop { get; }
}

// This implementation of ITest needs the user to set the value of Prop
class BTest : ATest
{
    string foo = "BTest";
    public override string Prop
    {
        get { return foo; }
        set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor
    }
}

// This implementation of ITest generates the value for Prop itself
class CTest : ATest
{
    string foo = "CTest";
    public override string Prop
    {
        get { return foo; }
        // set; // Not needed
    }
}

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.


2
Oder in meinem Fall {get; privates Set; }. Ich verstoße nicht gegen einen Vertrag, da das Set privat bleibt und sich nur mit der abgeleiteten Klasse befasst.
Machtyn

16

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:

  1. Erstellen Sie eine newEigenschaft, die sowohl a getals auch a setmit demselben Namen hat.

  2. Wenn Sie nichts anderes tun, wird die alte getMethode weiterhin aufgerufen, wenn die abgeleitete Klasse über ihren Basistyp aufgerufen wird. Um dies zu beheben, fügen Sie eine abstractZwischenebene hinzu, die overridedie alte getMethode verwendet, um sie zu zwingen, getdas Ergebnis der neuen Methode zurückzugeben.

Dies ermöglicht es uns, Eigenschaften mit get/ zu überschreiben, setselbst wenn sie in ihrer Basisdefinition keine hatten.

Als Bonus können Sie bei Bedarf auch die Rückgabeart ändern.

  • Wenn die getBasisdefinition -only war, können Sie einen stärker abgeleiteten Rückgabetyp verwenden.

  • Wenn die setBasisdefinition -only war, können Sie uns einen weniger abgeleiteten Rückgabetyp geben.

  • Wenn die Basisdefinition bereits get/ war set, 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 geterstellen.

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: getBestehendes Eigentum

Sie 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 seteiner Eigenschaft eine Methode hinzufügen , können dies jedoch nicht.

public abstract class A                     // Pre-existing class; can't modify
{
    public abstract int X { get; }          // You want a setter, but can't add it.
}
public class B : A                          // Pre-existing class; can't modify
{
    public override int X { get { return 0; } }
}

Problem: Kann nicht overridedas get-nur mit get/set

Sie möchten overridemit einer get/ setEigenschaft, aber es wird nicht kompiliert.

public class C : B
{
    private int _x;
    public override int X
    {
        get { return _x; }
        set { _x = value; }   //  Won't compile
    }
}

Lösung: Verwenden Sie eine abstractZwischenschicht

Sie können zwar nicht direkt overridemit einer get/ set-Eigenschaft arbeiten, aber Sie können :

  1. Erstellen Sie eine new get/ set-Eigenschaft mit demselben Namen.

  2. overridedie alte getMethode mit einem Accessor für die neue getMethode, um die Konsistenz sicherzustellen.

Also schreiben Sie zuerst die abstractZwischenschicht:

public abstract class C : B
{
    //  Seal off the old getter.  From now on, its only job
    //  is to alias the new getter in the base classes.
    public sealed override int X { get { return this.XGetter; }  }
    protected abstract int XGetter { get; }
}

Dann schreiben Sie die Klasse, die nicht früher kompiliert werden würde. Diesmal wird es kompiliert, da Sie nicht overridedie getEigenschaft -only verwenden. Stattdessen ersetzen Sie es durch das newSchlüsselwort.

public class D : C
{
    private int _x;
    public new virtual int X { get { return this._x; } set { this._x = value; } }

    //  Ensure base classes (A,B,C) use the new get method.
    protected sealed override int XGetter { get { return this.X; } }
}

Ergebnis: Alles funktioniert!

Offensichtlich funktioniert dies wie vorgesehen D.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

test.X = 7;
Print(test.X);      // Prints "7", as intended.

Alles funktioniert immer noch wie beabsichtigt, wenn es Dals eine seiner Basisklassen angezeigt wird, z . B. Aoder B. Der Grund, warum es funktioniert, ist jedoch möglicherweise weniger offensichtlich.

var test = new D() as B;
//test.X = 7;       // This won't compile, because test looks like a B,
                    // and B still doesn't provide a visible setter.

Die Definition der Basisklasse von getwird jedoch letztendlich immer noch von der Definition der abgeleiteten Klasse überschrieben get, sodass sie immer noch vollständig konsistent ist.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

var baseTest = test as A;
Print(test.X);      // Prints "7", as intended.

Diskussion

Mit dieser Methode können Sie -only-Eigenschaften setMethoden gethinzufügen. Sie können es auch verwenden, um Dinge zu tun wie:

  1. Ändern Sie jede Eigenschaft in eine get-only-, set-only- oder get-and- -Eigenschaft set, unabhängig davon, was sie in einer Basisklasse war.

  2. Ändern Sie den Rückgabetyp einer Methode in abgeleiteten Klassen.

Die Hauptnachteile sind, dass es mehr Codierung und ein zusätzliches abstract classim Vererbungsbaum gibt. Dies kann bei Konstruktoren, die Parameter verwenden, etwas ärgerlich sein, da diese in die Zwischenebene kopiert / eingefügt werden müssen.


Gab dies seine eigene Frage: stackoverflow.com/questions/22210971
Nat

Netter Ansatz (+1). Ich bezweifle jedoch, dass dies mit dem Entity Framework reibungslos funktioniert.
Mike de Klerk

9

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:

   class BaseType
   {
      public virtual T LastRequest { get {...} }
   }

   class DerivedTypeStrategy1
   {
      /// get or set the value returned by the LastRequest property.
      public bool T LastRequestValue { get; set; }

      public override T LastRequest { get { return LastRequestValue; } }
   }

   class DerivedTypeStrategy2
   {
      /// set the value returned by the LastRequest property.
      public bool SetLastRequest( T value ) { this._x = value; }

      public override T LastRequest { get { return _x; } }

      private bool _x;
   }

DateTime.Now ist rein funktional, da es keine Nebenwirkungen hat (die Tatsache, dass die Ausführung einige Zeit in Anspruch nimmt, kann möglicherweise als Nebeneffekt bezeichnet werden, aber da die Ausführungszeit - zumindest wenn sie begrenzt und kurz ist - nicht berücksichtigt wird Als Nebeneffekt einer anderen Methode würde ich sie auch hier nicht als eine betrachten.
Supercat

1
Hallo Superkatze. Sie haben Recht mit extern beobachtbaren Nebenwirkungen, die verhindern, dass eine Funktion rein ist. Die andere Einschränkung besteht darin, dass ein Ergebniswert für eine reine Funktion nur von den Parametern abhängen darf . Eine rein statische Eigenschaft muss daher als Funktion sowohl unveränderlich als auch konstant sein. Ein ausgefeilter Compiler kann alle nachfolgenden Aufrufe einer reinen Funktion ohne Parameter ersetzen, wobei das Ergebnis vom ersten Aufruf zurückgegeben wird.
T.Tobler

Sie haben Recht, dass meine Terminologie ungenau war, aber ich denke, dass die Angemessenheit einer schreibgeschützten Eigenschaft und nicht einer Methode eher von einem Mangel an Nebenwirkungen als von Reinheit abhängt.
Supercat

2

Sie können das Problem möglicherweise umgehen, indem Sie eine neue Eigenschaft erstellen:

public new int Bar 
{            
    get { return 0; }
    set {}        
}

int IBase.Bar { 
  get { return Bar; }
}

4
Sie können dies jedoch nur tun, wenn Sie eine Schnittstelle implementieren, nicht, wenn Sie von einer Basisklasse erben. Sie müssten einen anderen Namen wählen.
Svick

Ich gehe davon aus, dass die Beispielklasse IBase implementiert (so etwas wie die öffentliche Schnittstelle IBase {int Bar {get;}}), andernfalls ist bei der Implementierung der Klasse int IBase.Bar {...} nicht zulässig. Dann können Sie einfach eine reguläre Eigenschaftsleiste mit get und set erstellen, falls für die Bar-Eigenschaft der Benutzeroberfläche keine Festlegung erforderlich ist.
Ibrahim ben Salah

1

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:

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get;
        private set;
    }
}

IMO, C # sollte solche Szenarien nicht einschränken. Es liegt in der Verantwortung des Entwicklers, es entsprechend zu verwenden.


Ich verstehe deinen Standpunkt nicht. Stimmen Sie zu, dass C # das Hinzufügen eines Setters ermöglichen sollte, oder sind Sie mit den meisten anderen Personen zusammen, die diese Frage beantwortet haben?
Ripper234

2
Wie gesagt, IMO, C # sollte das Hinzufügen eines Setters ermöglichen. Es liegt in der Verantwortung des Entwicklers, es entsprechend zu verwenden.
Thomas Danecker

1

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.


0

Hier ist eine Problemumgehung, um dies mithilfe von Reflection zu erreichen:

var UpdatedGiftItem = // object value to update;

foreach (var proInfo in UpdatedGiftItem.GetType().GetProperties())
{
    var updatedValue = proInfo.GetValue(UpdatedGiftItem, null);
    var targetpropInfo = this.GiftItem.GetType().GetProperty(proInfo.Name);
    targetpropInfo.SetValue(this.GiftItem, updatedValue,null);
}

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!


1
Sind Sie sicher, dass Sie einen Setter über eine nicht vorhandene Reflektion aufrufen können (wie in den beschriebenen Nur-Getter-Eigenschaften)?
ODER Mapper

0

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.

public int GetBar(){}

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:

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    private int _bar;
    public override int Bar
    {
        get { return _bar; }
    }
    public void SetBar(int value)
    {
        _bar = value;
    }
}

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:

public abstract class BaseClass {
    protected int _bar;
    public int Bar { get { return _bar; } }
    protected void DoBaseStuff()
    {
        SetBar();
        //Do something with _bar;
    }
    protected abstract void SetBar();
}

public class ConcreteClass : BaseClass {
    protected override void SetBar() { _bar = 5; }
}

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:

public abstract class BaseClass
{
    public int Bar { get; }
}

Die Implementierung wird jedoch zwei Enden des Komplexitätsspektrums haben:

Am wenigsten komplex:

public abstract class BaseClass
{
    private int _bar;
    public int Bar { 
        get{
            return _bar;
        }}
    public void SetBar(int value) { _bar = value; }
}

Am komplexesten:

public abstract class BaseClass
{
    private int _foo;
    private int _baz;
    private int _wtf;
    private int _kthx;
    private int _lawl;

    public int Bar
    {
        get { return _foo * _baz + _kthx; }
    }
    public bool TryDoSomethingBaz(MyEnum whatever, int input)
    {
        switch (whatever)
        {
            case MyEnum.lol:
                _baz = _lawl + input;
                return true;
            case MyEnum.wtf:
                _baz = _wtf * input;
                break;
        }
        return false;
    }
    public void TryBlowThingsUp(DateTime when)
    {
        //Some Crazy Madeup Code
        _kthx = DaysSinceEaster(when);
    }
    public int DaysSinceEaster(DateTime when)
    {
        return 2; //<-- calculations
    }
}
public enum MyEnum
{
    lol,
    wtf,
}

Mein Punkt ist, wie auch immer, Sie haben den Setter ausgesetzt. In Ihrem Fall möchten Sie möglicherweise überschreiben, int Barweil 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.


0

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.

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    public override int Bar { get; }

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}

-1

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.


1
-1: Mit einem Setter kann ein Nachkomme nicht automatisch Invarianten brechen. Der Setter kann immer noch nur Dinge ändern, die ohnehin bereits vom Nachkommen geändert werden können.
Roman

@ RomanStarkov Das stimmt. Aber die Frage dieses Beitrags ist; Warum kann man einer Abstraktion keinen Setter hinzufügen, der Sie nicht dazu verpflichtet? Ihr Kommentar wäre in der entgegengesetzten Frage angemessen; Warum können Sie mit Microsoft eine ReadOnly-Eigenschaft in einer Abstraktion erstellen, wenn der Nachkomme ohnehin die volle Kontrolle über seine privaten Felder hat?
Suamere

+1: Bei dieser Frage geht es darum, warum Microsoft Ihnen nicht erlaubt, einen Vertrag zu brechen. Der Grund für Ihre Abstimmungen liegt jedoch eher darin, warum Microsoft zulässt, dass ein Vertrag nur schreibgeschützte Eigenschaften enthält. Diese Antwort ist also im Kontext richtig.
Suamere

@Suamere Der Vertrag besagt nicht, dass die Eigenschaft nicht geändert werden kann. Es heißt lediglich, dass eine Eigenschaft vorhanden ist, die gelesen werden kann. Durch Hinzufügen eines Setters wird dieser Vertrag nicht gebrochen. Sie können es als Absicht interpretieren , eine schreibgeschützte Eigenschaft zu erstellen, aber Sie können in C # 6 nicht garantieren, dass es keine Möglichkeit gibt, es zu ändern, daher ist der Vertrag, an den Sie denken, nicht wirklich vorhanden. Denken Sie an eine Schnittstelle: Sie kann einen Vertrag für eine gettable-Immobilie (nicht nur für get-only!) Anbieten. Diese Schnittstelle kann durch eine get + set-Eigenschaft problemlos implementiert werden.
Roman Starkov

Sie nähern sich dem aus der Sicht des konkreten Autors. Er kann es ändern, wie es ihm gefällt. Ich nähere mich dem aus der Perspektive des Basisklassenautors -> Annäherung als Endergebniskonsument der implementierenden Klasse. Der Autor der Basisklasse möchte nicht, dass ein Verbraucher der zukünftigen konkreten Klassen diese Eigenschaft ändern kann, ohne dass der Autor der konkreten Klasse eine spezielle Methode für die Einzelfallbehandlung bereitstellt. Denn in der Basisklasse wird diese Eigenschaft als unveränderlich behandelt und dies wird nur dadurch kommuniziert, dass sie als solche verfügbar gemacht wird.
Suamere

-1

Das ist nicht unmöglich. Sie müssen lediglich das Schlüsselwort "new" in Ihrer Immobilie verwenden. Beispielsweise,

namespace {
    public class Base {
        private int _baseProperty = 0;

        public virtual int BaseProperty {
            get {
                return _baseProperty;
            }
        }

    }

    public class Test : Base {
        private int _testBaseProperty = 5;

        public new int BaseProperty {
            get {
                return _testBaseProperty;
            }
            set {
                _testBaseProperty = value;
            }
        }
    }
}

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


29
Diese Antwort ist falsch, da die markierte Eigenschaft newdie virtuelle Basis nicht mehr überschreibt. Insbesondere wenn die Basiseigenschaft wie im ursprünglichen Beitrag abstrakt ist , ist es unmöglich, das newSchlüsselwort in einer nicht abstrakten Unterklasse zu verwenden, da Sie alle abstrakten Mitglieder überschreiben müssen.
Timwi

Dies ist phänomenal gefährlich, da die folgenden Ergebnisse unterschiedliche Ergebnisse liefern, obwohl sie dasselbe Objekt sind: 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.
Mark Sowul

Ich stimme w / Mark zu, das ist extrem böse. Wenn beide BaseProperty-Implementierungen von derselben Variablen unterstützt würden, wäre dies meiner Meinung nach in Ordnung. IE: _baseProperty schützen und _testBaseProperty abschaffen. Außerdem muss die Implementierung von AFAIK in Base nicht virtuell sein, da Sie sie nicht tatsächlich überschreiben.
Steazy

Die meisten Antworten auf Fragen können eine gültige Meinung und Eingabe haben. Abgesehen von den vorherigen Kommentaren ist meine Hauptkritik an dieser Antwort, dass ich glaube, dass die eigentliche Frage buchstäblich abstrakte Eigenschaften in einer Codebasis betrifft, über die der Verbraucher keine Kontrolle hat. Meine Kritik ist also, dass diese Antwort möglicherweise nicht zur Frage passt.
Suamere

-1

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".


1
Eine ReadableMatrixKlasse (mit einer schreibgeschützten indizierten Eigenschaft mit zwei Argumenten) mit Untertypen ImmutableMatrixund würde keine Unbeholfenheit implizieren MutableMatrix. 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 akzeptieren ReadableMatrixund mit veränderlichen oder unveränderlichen Subtypen vollkommen zufrieden sein.
Supercat

Ein zusätzlicher Setter könnte eine private Variable innerhalb der Klasse ändern. Die sogenannte "schreibgeschützte" Eigenschaft aus der Basisklasse würde dann immer noch den Wert "aus der Klasse heraus" bestimmen (durch Lesen der neuen privaten Variablen). Ich sehe das Problem dort nicht. Schauen List<T>.CountSie 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.
ODER Mapper

-2

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.


2
"Die Außenwelt" interessiert mich hier nicht besonders. Ich möchte einer Klasse Funktionen hinzufügen, die immer nur für Code sichtbar sind, der die bestimmte Klasse und nicht die Basis kennt.
Ripper234

@ ripper234 Siehe die Antwort von Matt Bolt: Wenn Sie die neue Eigenschaft nur für Benutzer der abgeleiteten Klasse sichtbar machen möchten, sollten Sie newstattdessen verwenden override.
Dan Berindei

1
Es bleibt nicht verborgen. Sie möchten sagen, dass Sie einer untergeordneten Klasse keine neue Methode hinzufügen können, da die neue Methode "vor der Außenwelt verborgen bleibt". Natürlich bleibt es aus Sicht der Basisklasse verborgen, aber es ist für die Clients der untergeordneten Klasse zugänglich. (Dh zu beeinflussen, was der Getter zurückgeben soll)
Sheepy

Sheepy, das sage ich überhaupt nicht. Ich meine, genau wie Sie betont haben, würde es "aus Sicht der Basisklasse" verborgen bleiben. Deshalb habe ich "was die Benutzeroberfläche Ihrer Klassen betrifft" angehängt . Wenn Sie eine Basisklasse unterordnen, ist es fast sicher, dass sich die "Außenwelt" auf Ihre Objektinstanzen mit dem Basisklassentyp bezieht. Wenn sie Ihre konkrete Klasse direkt verwenden würden, benötigen Sie keine Kompatibilität mit der Basisklasse, und Sie könnten sie auf jeden Fall newmit Ihren Ergänzungen verwenden.
Ishmaeel

-2

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.


Up-Vote. Das ist sehr prägnant. Warum sollte Microsoft dem Verbraucher einer Basisklasse erlauben, den Vertrag zu brechen? Ich stimme dieser sehr kurzen Version meiner zu langen und verwirrenden Antwort zu. Lol. Wenn Sie ein Verbraucher einer Basisklasse sind, sollten Sie den Vertrag nicht brechen wollen, aber Sie können ihn auf verschiedene Arten implementieren.
Suamere
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.