it-swarm-eu.dev

Warum ist es nicht möglich, eine Nur-Getter-Eigenschaft zu überschreiben und einen Setter hinzuzufügen?

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

130
ripper234

Der Autor von Baseclass hat ausdrücklich erklärt, dass Bar eine schreibgeschützte Eigenschaft sein muss. Es ist für Ableitungen nicht sinnvoll, diesen Vertrag zu brechen und ihn schreibgeschützt zu machen.

Ich bin bei Microsoft dabei.
Angenommen, ich bin ein neuer Programmierer, dem gesagt wurde, dass er gegen die Baseclass-Ableitung kodieren soll. Ich schreibe etwas, in dem davon ausgegangen wird, dass Bar nicht beschrieben werden kann (da Baseclass ausdrücklich angibt, dass es sich um eine get-get-Eigenschaft handelt). 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 wie in der BaseClass-Benutzeroberfläche festgelegt werden kann, geht BarProvider davon aus, dass das Zwischenspeichern eine sichere Sache ist - Since Bar kann nicht geändert werden. Wenn in einer Ableitung ein Satz möglich war, könnte diese Klasse veraltete Werte liefern, wenn jemand die Bar-Eigenschaft des _source-Objekts extern geändert hat. Der Punkt ist "Sei offen, vermeide hinterlistige Dinge und überrasche die Leute"

Update : Ilya Ryzhenkov fragt 'Warum spielen Schnittstellen dann nicht nach den gleichen Regeln?' Hmm .. das wird immer matschiger, wenn ich daran denke.
Eine Schnittstelle ist ein Vertrag, der besagt, dass von einer Implementierung eine Leseeigenschaft namens Bar erwartet wird. Persönlich Wenn ich ein Interface gesehen habe, bin ich viel seltener der Ansicht, dass nur Lesezugriff möglich ist. Wenn ich eine get-only-Eigenschaft auf einer Schnittstelle sehe, lese ich sie als "Jede Implementierung würde dieses Attribut Bar verfügbar machen" ... auf eine Basisklasse klickt sie als "Bar ist eine schreibgeschützte Eigenschaft". Natürlich brechen Sie technisch nicht den Vertrag. Sie tun mehr. Sie haben also in gewisser Weise recht. Ich möchte abschließend sagen, dass Missverständnisse so schwer wie möglich auftauchen sollen.

1
Gishu

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

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

es ist ziemlich eindeutig, dass sowohl die get als auch die set Überschreibungen sind. Es gibt keine set in der Basisklasse, daher beschwert sich der Compiler. So wie Sie eine Methode nicht überschreiben können, die nicht in der Basisklasse definiert ist, können Sie auch keinen Setter überschreiben.

Man könnte sagen, der Compiler sollte Ihre Absicht erraten und die Überschreibung nur auf die Methode anwenden, die überschrieben werden kann (in diesem Fall also der Getter). Dies widerspricht jedoch einem der C # -Designprinzipien - der Compiler darf Ihre Absichten nicht erraten , weil es falsch sein kann, ohne dass Sie es wissen.

Ich denke, die folgende Syntax mag gut sein, aber wie Eric Lippert immer wieder sagt, ist die Implementierung selbst eines kleineren Features immer noch ein erheblicher Aufwand ...

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

oder für automatisch implementierte Eigenschaften

public int MyProperty { override get; set; }
31
Roman Starkov

Ich bin heute über dasselbe Problem gestolpert und denke, dass es einen sehr guten Grund gibt, dies zu wollen. 

Zunächst möchte ich argumentieren, dass eine Nur-Get-Eigenschaft nicht unbedingt in schreibgeschützt übersetzt werden kann. Ich interpretiere es als "Von dieser Schnittstelle/Abtract können Sie diesen Wert erhalten", das bedeutet nicht, dass einige Implementierungen dieser Schnittstelle/abstrakten Klasse den Benutzer/das Programm nicht benötigen, 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 vererbte Klasse keinen Setter hinzufügen kann, ohne gegen Verträge zu verstoßen.

Das Folgende ist ein vereinfachtes Beispiel für das, was ich heute brauchte. Ich musste am Ende einen Setter in mein Interface einbauen, um das 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 geworden 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 "2 Cent" -Post ist, aber ich fühle mit dem ursprünglichen Poster und versuche zu rationalisieren, dass dies eine gute Sache ist, was mir seltsam erscheint, vor allem wenn man bedenkt, dass die gleichen Einschränkungen nicht gelten, wenn man direkt von einem erbt Schnittstelle.

Die Erwähnung über die Verwendung von new anstelle von override trifft hier nicht zu, sie funktioniert einfach nicht und selbst wenn, würde sie nicht das gewünschte Ergebnis liefern, nämlich einen virtuellen Getter, wie er von der Benutzeroberfläche beschrieben wird.

19
JJJ

Es ist möglich

tl; dr - Sie können eine Nur-Holen-Methode mit einem Setter überschreiben, wenn Sie möchten. Es ist im Grunde nur:

  1. Erstellen Sie eine Eigenschaft new, die sowohl eine get als auch eine set mit demselben Namen enthält.

  2. Wenn Sie nichts anderes tun, wird die alte Methode get weiterhin aufgerufen, wenn die abgeleitete Klasse über ihren Basistyp aufgerufen wird. Um dies zu beheben, fügen Sie eine abstract - Zwischenebene hinzu, die override für die alte get - Methode verwendet, um zu erzwingen, dass das Ergebnis der neuen get - Methode zurückgegeben wird.

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

Als Bonus können Sie auch die Rückgabeart ändern, wenn Sie möchten.

  • Wenn die Basisdefinition nur get war, können Sie einen weiter abgeleiteten Rückgabetyp verwenden.

  • Wenn die Basisdefinition nur set war, können Sie einen weniger abgeleiteten Rückgabetyp verwenden.

  • Wenn die Basisdefinition bereits get/set war, dann:

    • sie können einen weiter abgeleiteten Rückgabetyp verwenden , wenn Sie es nur set - machen;

    • sie können einen weniger abgeleiteten Rückgabetyp verwenden , wenn Sie es nur get - machen.

In allen Fällen können Sie den gleichen Rückgabetyp beibehalten, wenn Sie möchten. In den folgenden Beispielen wird zur Vereinfachung derselbe Rückgabetyp verwendet.

Situation: Bereits vorhandene get - nur Eigenschaft

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 einer Eigenschaft eine set - 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 override das get - nur mit get/set

Sie möchten override mit einer Eigenschaft get/set, diese wird jedoch 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 Zwischenschicht abstract

Während Sie mit einer override -/get - Eigenschaft nicht direkt set können, können Sie :

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

  2. override die alte get - Methode mit einem Zugriff auf die neue get - Methode, um die Konsistenz sicherzustellen.

Also schreiben Sie zuerst die Zwischenebene abstract:

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 zuvor nicht kompiliert wurde. Diesmal wird es kompiliert, da Sie nicht die einzige Eigenschaft overrideget verwenden. Stattdessen ersetzen Sie es mit dem Schlüsselwort new.

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 für D wie vorgesehen.

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 weiterhin wie vorgesehen, wenn D als eine seiner Basisklassen angezeigt wird, z. A oder B. Aber der Grund, warum es funktioniert, könnte ein wenig weniger offensichtlich sein.

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 Basisklassendefinition von get wird jedoch letztendlich immer noch von der Definition der abgeleiteten Klasse von get überschrieben, 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 set - Methoden nur zu get - Eigenschaften hinzufügen. Sie können es auch verwenden, um Dinge zu tun wie:

  1. Ändern Sie eine Eigenschaft in eine get - only-, set - only- oder get - und - set -Eigenschaft, unabhängig davon, was sich in einer Basisklasse befand .

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

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

14
Nat

Ich bin damit einverstanden, dass die Möglichkeit, einen Getter in einem abgeleiteten Typ nicht zu überschreiben, ein Anti-Pattern ist. Schreibgeschützt gibt an, dass keine Implementierung erfolgt, und nicht ein reiner Funktionsvertrag (impliziert durch die Antwort auf die erste Abstimmung).

Ich habe den Verdacht, dass Microsoft diese Einschränkung hatte, entweder weil das gleiche Missverständnis befürwortet wurde, oder vielleicht, weil die Grammatik vereinfacht wurde. Jetzt, da dieser Bereich angewendet werden kann, um einzeln abgerufen oder festgelegt zu werden, können wir vielleicht hoffen, dass Überschreiben auch möglich ist.

Das Missverständnis der Top-Vote-Antwort, dass eine Nur-Lese-Eigenschaft irgendwie "reiner" sein sollte als eine Read/Write-Eigenschaft, ist lächerlich. Schauen Sie sich einfach viele allgemeine schreibgeschützte Eigenschaften im Framework an. der Wert ist nicht konstant/rein funktional; DateTime.Now ist beispielsweise schreibgeschützt, aber alles andere als ein reiner Funktionswert. Ein Versuch, einen Wert einer schreibgeschützten Eigenschaft zu "cachen", vorausgesetzt, er wird beim nächsten Mal denselben Wert zurückgeben, ist riskant.

In jedem Fall habe ich eine der folgenden Strategien angewandt, um diese Einschränkung zu überwinden. Beide sind weniger als perfekt, erlauben Ihnen jedoch, über diesen Sprachmangel hinauszuhinken:

   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;
   }
8
T.Tobler

Das Problem ist, dass Microsoft aus irgendeinem Grund entschieden hat, dass es drei verschiedene Arten von Eigenschaften geben sollte: Nur Lesen, Nur Schreiben und Nur Lesen. Nur eine davon kann mit einer bestimmten Signatur in einem bestimmten Kontext vorhanden sein. Eigenschaften können nur durch identisch deklarierte Eigenschaften überschrieben werden. Um das zu tun, was Sie möchten, müssen Sie zwei Eigenschaften mit demselben Namen und derselben Signatur erstellen - eine davon war schreibgeschützt, und eine davon war schreibgeschützt.

Ich persönlich wünsche mir, dass das gesamte Konzept der "Eigenschaften" abgeschafft werden könnte, mit der Ausnahme, dass die property-ish-Syntax als syntaktischer Zucker verwendet werden kann, um "get" - und "set" -Methoden zu nennen. Dies würde nicht nur die Option "Set hinzufügen" erleichtern, sondern es würde auch ermöglichen, dass "get" einen anderen Typ als "set" zurückgibt. Während eine solche Fähigkeit nicht allzu 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 akzeptieren kann.

1
supercat

Ich kann alle Ihre Punkte verstehen, aber die automatischen Eigenschaften von C # 3.0 werden in diesem Fall nutzlos.

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, ihn entsprechend zu verwenden.

1
Thomas Danecker

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; }
}
1
Hallgrim

Lösung für nur einen kleinen Teil der Anwendungsfälle, aber trotzdem: In C # 6.0 wird der "readonly" -Setter automatisch für überschriebene 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;
    }
}
0
lxa

Sie sollten Ihren Fragetitel so ändern, dass entweder Ihre Frage nur das Überschreiben einer abstrakten Eigenschaft betrifft oder dass sich Ihre Frage auf das generelle Überschreiben der Nur-Eigenschaft einer Klasse bezieht.


Wenn das vorherige (eine abstrakte Eigenschaft überschreibt)

Dieser Code ist nutzlos. Eine Basisklasse allein sollte Ihnen nicht sagen, dass Sie eine Get-Only-Eigenschaft (möglicherweise eine Schnittstelle) überschreiben müssen. Eine Basisklasse stellt eine allgemeine Funktionalität bereit, die möglicherweise eine spezifische Eingabe von einer implementierenden Klasse erfordert. Daher kann die allgemeine Funktionalität zu abstrakten Eigenschaften oder Methoden führen. In diesem Fall sollten die gängigen Funktionsmethoden Sie dazu auffordern, eine abstrakte Methode zu überschreiben, z.

public int GetBar(){}

Wenn Sie jedoch keine Kontrolle darüber haben und die Funktionalität der Basisklasse von ihrer eigenen öffentlichen Eigenschaft (seltsam) gelesen wird, tun Sie dies einfach:

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, es ist eine Best-Practice, 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; }
}

If letzteres (überschreibt die Nur-Eigenschaft einer Klasse)

Jede nicht abstrakte Eigenschaft hat einen Setter. Ansonsten ist es nutzlos und Sie sollten es nicht verwenden. Microsoft muss nicht zulassen, dass Sie tun, was Sie möchten. Der Grund dafür ist: Der Setter existiert in irgendeiner Form und Sie können das, was Sie wollen, Veerryy leicht erreichen.

Die Basisklasse oder jede Klasse, in der Sie eine Eigenschaft mit {get;} lesen können, enthält EINE ART von exponierter Setter für diese Eigenschaft. Die Metadaten werden folgendermaßen aussehen:

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

Die Implementierung hat jedoch zwei Enden des Komplexitätsspektrums:

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, auf jeden Fall haben Sie den Setter ausgesetzt. In Ihrem Fall möchten Sie vielleicht int Bar überschreiben, weil Sie nicht möchten, dass die Basisklasse damit zurechtkommt, keinen Zugriff darauf hat, wie er damit umgeht, oder dass Sie dazu aufgefordert wurden, etwas Code in die richtige Richtung zu bekommen werden.

In letzterer und früherer (Schlussfolgerung)

Kurzbeschreibung: Es ist für Microsoft nicht notwendig, etwas zu ändern. Sie können auswählen, wie Ihre implementierende Klasse eingerichtet ist, und ohne Konstruktor alle oder keine Basisklasse verwenden.

0
Suamere

Hier ist eine Problemumgehung, um dies mit 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 den Objektwert für eine Eigenschaft festlegen, die nur gelesen werden kann. Kann aber nicht in allen Szenarien funktionieren!

0
user2514880