it-swarm.it

Perché è impossibile sovrascrivere una proprietà getter-only e aggiungere un setter?

Perché il seguente codice C # non è consentito:

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': impossibile eseguire l'override perché 'BaseClass.Bar' non ha un set di accesso sovrascrivibile

130
ripper234

Perché lo scrittore di Baseclass ha dichiarato esplicitamente che la barra deve essere una proprietà di sola lettura. Non ha senso che le derivazioni rompere questo contratto e renderlo in lettura-scrittura.

Sono con Microsoft su questo.
Diciamo che sono un nuovo programmatore a cui è stato detto di codificare la derivazione di Baseclass. scrivo qualcosa che presuppone che non si possa scrivere su Bar (poiché la Baseclass afferma esplicitamente che è una proprietà get only) . Ora con la tua derivazione, il mio codice potrebbe rompersi. per esempio.

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

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

  public Bar getBar()
  { return _currentBar;  }
}

Poiché Bar non può essere impostato secondo l'interfaccia BaseClass, BarProvider presume che la memorizzazione nella cache sia una cosa sicura da fare - poiché la barra non può essere modificata. Ma se era possibile impostare in una derivazione, questa classe poteva servire valori scaduti se qualcuno modificava la proprietà Barra dell'oggetto _source esternamente. Il punto è "Be Open, evita di fare cose subdole e persone sorprendenti"

Aggiornamento : Ilya Ryzhenkov chiede "Perché le interfacce non giocano secondo le stesse regole?" Hmm .. questo diventa più fangoso come ci penso.
Un'interfaccia è un contratto che dice "si aspetti che un'implementazione abbia una proprietà di lettura chiamata Bar". Personalmente Sono molto meno propenso a fare questa supposizione di sola lettura se ho visto un'interfaccia. Quando vedo una proprietà get-only su un'interfaccia, l'ho letto come "Qualsiasi implementazione esponeva questa barra degli attributi" ... su una classe base fa clic su "La barra è una proprietà di sola lettura". Certo che tecnicamente non stai rompendo il contratto .. stai facendo di più. Quindi hai ragione, in un certo senso. Mi chiuderei dicendo "fai in modo che le incomprensioni si complichino il più possibile".

1
Gishu

Penso che la ragione principale sia semplicemente che la sintassi è troppo esplicita per far sì che funzioni in altro modo. Questo codice:

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

è abbastanza esplicito che sia get che set sono overrides. Non c'è set nella classe base, quindi il compilatore si lamenta. Proprio come non è possibile sovrascrivere un metodo che non è definito nella classe base, non è possibile sovrascrivere un setter.

Potresti dire che il compilatore dovrebbe indovinare la tua intenzione e applicare solo l'override al metodo che può essere sovrascritto (cioè il getter in questo caso), ma questo va contro uno dei principi di progettazione di C # - che il compilatore non deve indovinare le tue intenzioni , perché può indovinare sbagliato senza che tu lo sappia.

Penso che la seguente sintassi potrebbe fare bene, ma come continua a dire Eric Lippert, implementare anche una funzionalità minore come questa è ancora una grande quantità di sforzo ...

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

oppure, per le proprietà autoimplementate,

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

Oggi sono incappato nello stesso identico problema e penso che abbia una ragione molto valida per volerlo. 

Innanzitutto vorrei dire che avere una proprietà get-only non si traduce necessariamente in sola lettura. Lo interpreto come "Da questa interfaccia/astrazione puoi ottenere questo valore", questo non significa che alcune implementazioni di quell'interfaccia/classe astratta non necessitino che l'utente/programma imposti esplicitamente questo valore. Le classi astratte servono allo scopo di implementare parte delle funzionalità necessarie. Non vedo assolutamente alcun motivo per cui una classe ereditata non possa aggiungere un setter senza violare alcun contratto.

Quello che segue è un esempio semplificato di ciò di cui avevo bisogno oggi. Ho finito per dover aggiungere un setter nella mia interfaccia solo per aggirare questo. Il motivo per aggiungere il setter e non aggiungere, ad esempio, un metodo SetProp è che una particolare implementazione dell'interfaccia ha utilizzato DataContract/DataMember per la serializzazione di Prop, che sarebbe stata resa inutilmente complicata se avessi dovuto aggiungere un'altra proprietà solo per lo scopo di serializzazione.

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
    }
}

So che questo è solo un post dei "miei 2 centesimi", ma mi sento con il poster originale e sto cercando di razionalizzare che questa è una cosa buona mi sembra strana, specialmente considerando che le stesse limitazioni non si applicano quando si eredita direttamente da un interfaccia.

Anche la menzione sull'uso di nuove invece di sovrascrivere non si applica qui, semplicemente non funziona e anche se lo facesse non ti darebbe il risultato desiderato, vale a dire un getter virtuale come descritto dall'interfaccia.

19
JJJ

È possibile

tl; dr - È possibile sovrascrivere un metodo get-only con un setter se lo si desidera. È fondamentalmente solo:

  1. Crea una proprietà new che abbia sia get sia set usando lo stesso nome.

  2. Se non fai altro, allora il vecchio metodo get sarà ancora chiamato quando la classe derivata viene chiamata attraverso il suo tipo di base. Per risolvere questo problema, aggiungi un abstract livello intermedio che utilizza override sul vecchio metodo get per costringerlo a restituire il risultato del nuovo metodo get.

Questo ci consente di sovrascrivere le proprietà con getset anche se ne mancava una nella loro definizione di base.

Come bonus, puoi anche modificare il tipo di reso se lo desideri.

  • Se la definizione di base era solo get-, è possibile utilizzare un tipo di ritorno più derivato.

  • Se la definizione di base era solo set-, allora puoi scegliere un tipo di ritorno meno derivato.

  • Se la definizione di base era già get/set, quindi:

    • puoi usare un tipo di ritorno più derivato _/iflo fai set- solo;

    • puoi usare un tipo di ritorno meno derivatoiflo fai get- solo.

In tutti i casi, puoi mantenere lo stesso tipo di reso, se lo desideri. Gli esempi seguenti usano lo stesso tipo di ritorno per semplicità.

Situazione: proprietà get-preesistente

Hai una struttura di classe che non puoi modificare. Forse è solo una classe, o è un albero ereditario preesistente. In ogni caso, si desidera aggiungere un metodo set a una proprietà, ma non è possibile.

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

Problema: non overrideget- solo con getset

Si desidera override con una proprietà get/set, ma non verrà compilata.

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

Soluzione: utilizzare un livello intermedio abstract

Mentre non puoi direttamente override con una proprietà get/set, puoi _/can:

  1. Creare una proprietà newget/set con lo stesso nome.

  2. override il vecchio metodo get con un accessore al nuovo metodo get per garantire la coerenza.

Quindi, per prima cosa scrivi il livello intermedio 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; }
}

Quindi, scrivi la classe che non si compilerebbe prima. Questa volta verrà compilato perché in realtà non override'e la proprietà get- only; invece, lo stai sostituendo usando la parola chiave 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; } }
}

Risultato: tutto funziona!

Ovviamente, questo funziona come previsto per 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.

Tutto funziona ancora come previsto quando si visualizza D come una delle sue classi base, ad es. A o B. Ma la ragione per cui funziona potrebbe essere un po 'meno ovvia.

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.

Tuttavia, la definizione della classe base di get è ancora definitivamente sostituita dalla definizione della classe derivata get, quindi è ancora completamente coerente.

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.

Discussione

Questo metodo consente di aggiungere i metodi set alle proprietà get- only. Puoi anche usarlo per fare cose come:

  1. Modificare qualsiasi proprietà in una proprietà get- only, set- only o get- e -set, indipendentemente da cosa fosse in una classe base.

  2. Cambia il tipo di ritorno di un metodo nelle classi derivate.

Gli svantaggi principali sono che c'è più codice da fare e un abstract class extra nell'albero di ereditarietà. Questo può essere un po 'fastidioso con costruttori che prendono parametri perché questi devono essere copiati/incollati nel livello intermedio.

12
Nat

Sono d'accordo che non essere in grado di scavalcare un getter in un tipo derivato è un anti-pattern. Read-Only specifica la mancanza di implementazione, non un contratto di pura funzionalità (implicita nella risposta del voto più alto).

Sospetto che Microsoft abbia avuto questa limitazione o perché è stato promosso lo stesso equivoco, o forse a causa della semplificazione della grammatica; tuttavia, ora che lo scope può essere applicato per ottenere o impostare individualmente, forse possiamo anche sperare che l'override sia troppo.

L'equivoco indicato dalla risposta del voto più alto, che una proprietà di sola lettura dovrebbe in qualche modo essere più "pura" di una proprietà di lettura/scrittura è ridicola. Basta guardare molte proprietà comuni di sola lettura nel framework; il valore non è costante/puramente funzionale; ad esempio, DateTime.Now è di sola lettura, ma tutto tranne un puro valore funzionale. Un tentativo di "memorizzare in cache" un valore di una proprietà di sola lettura supponendo che restituirà lo stesso valore la prossima volta è rischioso.

In ogni caso, ho usato una delle seguenti strategie per superare questa limitazione; entrambi sono meno che perfetti, ma ti permetteranno di zoppicare oltre questa mancanza di linguaggio:

   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

Il problema è che per qualsiasi motivo Microsoft ha deciso che dovrebbero esserci tre diversi tipi di proprietà: sola lettura, sola scrittura e lettura-scrittura, solo una delle quali può esistere con una determinata firma in un dato contesto; le proprietà possono essere sostituite solo da proprietà identicamente dichiarate. Per fare ciò che vuoi, sarebbe necessario creare due proprietà con lo stesso nome e firma - una delle quali era di sola lettura, e una delle quali era di lettura-scrittura.

Personalmente, vorrei che l'intero concetto di "proprietà" potesse essere abolito, tranne che la sintassi di proprietà-ish potrebbe essere usata come zucchero sintattico per chiamare i metodi "ottieni" e "imposta". Ciò non solo faciliterebbe l'opzione "aggiungi set", ma consentirebbe anche a "get" di restituire un tipo diverso da "set". Sebbene tale capacità non venga usata molto spesso, a volte potrebbe essere utile avere un metodo 'get' per restituire un oggetto wrapper mentre il 'set' potrebbe accettare un wrapper o dati effettivi.

1
supercat

Posso capire tutti i tuoi punti, ma in effetti le proprietà automatiche del C # 3.0 diventano inutili in quel caso.

Non puoi fare nulla del genere:

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

IMO, C # non dovrebbe limitare tali scenari. È responsabilità dello sviluppatore utilizzarlo di conseguenza.

1
Thomas Danecker

Potresti forse aggirare il problema creando una nuova proprietà:

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

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

Soluzione solo per un piccolo sottoinsieme di casi d'uso, ma ciononostante: in C # 6.0 il setter "readonly" viene automaticamente aggiunto per le proprietà solo getter sovrascritte.

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

Dovresti modificare il titolo della tua domanda in entrambi i dettagli che la tua domanda riguarda esclusivamente l'override di una proprietà astratta o che la tua domanda riguarda generalmente la proprietà get-only di una classe.


Se il precedente (sovrascrivendo una proprietà astratta)

Quel codice è inutile. Una classe base da sola non dovrebbe dirti che sei costretto a sovrascrivere una proprietà Get-Only (forse un'interfaccia). Una classe base fornisce funzionalità comuni che possono richiedere input specifici da una classe di implementazione. Pertanto, la funzionalità comune può effettuare chiamate a proprietà o metodi astratti. Nel caso specifico, i metodi di funzionalità comuni dovrebbero richiedere all'utente di sovrascrivere un metodo astratto come:

public int GetBar(){}

Ma se non hai alcun controllo su questo, e la funzionalità della classe base legge dalla sua proprietà pubblica (strano), allora fai questo:

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

Voglio sottolineare il (strano) commento: direi che una best practice è che una classe non usi le sue proprietà pubbliche, ma che usi i suoi campi privati ​​/ protetti quando esistono. Quindi questo è uno schema migliore:

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

Se quest'ultimo (sovrascrivendo la proprietà get-only di una classe)

Ogni proprietà non astratta ha un setter. Altrimenti è inutile e non dovresti preoccuparti di usarlo. Microsoft non deve permettervi di fare ciò che volete. La ragione è: il setter esiste in una forma o nell'altra, e puoi realizzare ciò che vuoi Veerryy facilmente.

La classe base, o qualsiasi classe in cui è possibile leggere una proprietà con {get;}, haQUALCHEsorta di setter esposto per quella proprietà. I metadati avranno questo aspetto:

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

Ma l'implementazione avrà due estremità dello spettro della complessità:

Complesso minimo:

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

Più complesso:

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,
}

Il mio punto di essere, in entrambi i casi, hai esposto il setter. Nel tuo caso, potresti voler sovrascrivere int Bar perché non vuoi che la classe base lo gestisca, non abbia accesso a rivedere come lo gestisce, o è stato incaricato di codificare un codice molto veloce e negativo contro il tuo volontà.

In both Latter and Former (Conclusione)

Short-Story: non è necessario che Microsoft cambi nulla. È possibile scegliere come impostare la classe di implementazione e, senza il costruttore, utilizzare tutti o nessuno della classe base.

0
Suamere

Ecco una soluzione per raggiungere questo obiettivo utilizzando Reflection:

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);
}

In questo modo possiamo impostare il valore dell'oggetto su una proprietà che è di sola lettura. Potrebbe non funzionare in tutti gli scenari però!

0
user2514880