Perché è impossibile eseguire l'override di un metodo getter-solo proprietà e aggiungere un setter?[chiuso]

StackOverflow https://stackoverflow.com/questions/82437

Domanda

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':non può ignorare perche 'BaseClass.Bar' non hanno un overridable funzione di accesso set

È stato utile?

Soluzione

Perché l'autore di Baseclass ha esplicitamente dichiarato che la Barra deve essere una proprietà di sola lettura.Non ha senso per derivazioni per la rottura di questo contratto e di renderlo di lettura-scrittura.

Io sto con Microsoft su questo.
Diciamo che sono un nuovo programmatore che è stato detto di codice contro Baseclass derivazione.scrivo qualcosa che si presuppone che il Bar non può essere scritto (dato che il Baseclass afferma esplicitamente che si tratta di un solo immobile).Ora, con la derivazione, il mio codice si possono rompere.ad es.

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

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

  public Bar getBar()
  { return _currentBar;  }
}

Dal Bar non può essere impostato come per il BaseClass interfaccia, BarProvider si presuppone che la cache è una cosa più sicura da fare - Dal Bar non può essere modificato.Ma se il set è stato possibile una derivazione, questa classe potrebbe essere che serve valori obsoleti se qualcuno ha modificato il _codice oggetto del Bar di proprietà esterno.Il punto di essere 'Essere Aperto, evitare di fare subdolo cose e sorprendente per la gente'

Aggiornamento: Ilya Ryzhenkov si chiede: "Perché non le interfacce di giocare con le stesse regole e quindi?' Hmm..questo si ottiene muddier come la penso su di esso.
L'interfaccia è un contratto che dice 'aspettare un'implementazione di avere una proprietà di lettura denominato Bar.' Personalmente Io sono molto meno probabilità di fare ipotesi di sola lettura se non ho visto un'Interfaccia.Quando vedo una proprietà di sola su un'interfaccia, l'ho letto come "Qualsiasi implementazione di esporre questo attributo Bar'...su una base di classe non scatta come 'Bar è una proprietà di sola lettura'.Ovviamente tecnicamente non stai violando il contratto..si sta facendo di più.Così hai ragione in un certo senso..Vorrei chiudere dicendo 'renderlo duro come il possibile per incomprensioni per ritagliare up'.

Altri suggerimenti

Penso che il motivo principale è semplicemente che la sintassi è troppo esplicito, per questo tipo di lavoro in qualsiasi altro modo.Questo codice:

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

è abbastanza esplicito che sia il get e il set sono sostituzioni.Non c'è set nella classe base, in modo che il compilatore si lamenta.Proprio come non è possibile eseguire l'override di un metodo che non è definita nella classe base, non è possibile eseguire l'override di un setter sia.

Si potrebbe dire che il compilatore deve indovinare la vostra intenzione e solo applicare l'override del metodo che può essere sottoposto a override (es.il getter in questo caso), ma questo va contro il C# principi di progettazione - che il compilatore non deve indovinare vostre intenzioni, perché può indovinare sbagliato senza di voi saperlo.

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

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

o, per autoimplemented proprietà,

public int MyProperty { override get; set; }

Mi sono imbattuto lo stesso problema di oggi e penso che avere un valido motivo per desiderare questo.

Prima di tutto vorrei argomentare che, avendo una proprietà di sola non necessariamente si traducono in sola lettura.Io lo interpreto come "Da questa interfaccia/abtract è possibile ottenere questo valore", che non significa che alcuni attuazione di tale interfaccia/classe astratta non avrete bisogno dell'utente/programma per impostare questo valore in modo esplicito.Classi astratte servire lo scopo di attuare parte delle funzionalità necessarie.Vedo assolutamente alcun motivo per cui una classe ereditata non potrebbe aggiungere un setter, senza violare alcun contratto.

Il seguente è un esempio semplificato di quello di cui avevo bisogno oggi.Ho finito per dover aggiungere un setter nella mia interfaccia solo per ottenere intorno a questo.Il motivo per aggiungere il setter e non aggiungendo, ad esempio, un SetProp metodo è che una particolare implementazione dell'interfaccia utilizzata DataContract/DataMember per la serializzazione dell'Elica, che sarebbe stato fatto inutilmente complicato, se ho 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 "my 2 cents" post, ma ho la sensazione che con la locandina originale e cercando di razionalizzare che questa è una buona cosa, mi sembra strano, soprattutto considerando che le stesse limitazioni non si applicano quando eredita direttamente da un'interfaccia.

Anche la menzione sull'utilizzo di nuovo, invece di ignorare non si applica qui, semplicemente non funziona, e anche se lo facesse non dare il risultato voluto, cioè un virtuale getter, come descritto dall'interfaccia.

È possibile

tl;dr- È possibile eseguire l'override di un metodo solo con un setter, se si desidera.È fondamentalmente solo:

  1. Creare un new la struttura che dispone di un get e un set utilizzando lo stesso nome.

  2. Se si non fare niente altro, allora il vecchio get metodo sarà ancora chiamata quando la classe derivata è chiamato per il suo tipo di base.Per risolvere questo problema, aggiungere un abstract strato intermedio che utilizza override sul vecchio get metodo per forzare per tornare la nuova get risultato del metodo.

Questo ci permette di modificare le proprietà con get/set anche se non è mancato uno in base alla loro definizione.

Come bonus, si può anche cambiare il tipo di ritorno, se si desidera.

  • Se la definizione di base è stato get-solo, quindi è possibile utilizzare un derivato tipo di ritorno.

  • Se la definizione di base è stato set-solo, quindi è possibile utilizzare un derivato tipo di ritorno.

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

    • è possibile utilizzare un derivato tipo di ritorno se si rendono set-solo;

    • è possibile utilizzare un derivato tipo di ritorno se si rendono get-solo.

In tutti i casi, è possibile mantenere lo stesso tipo di ritorno, se si desidera.Gli esempi qui riportati utilizzare lo stesso tipo di ritorno per la semplicità.

Situazione:Pre-esistenti getproprietà di sola

Avete qualche struttura di classe che non è possibile modificare.Forse è solo uno di una classe o di un pre-esistente struttura ereditata.Qualunque sia il caso, si desidera aggiungere un set metodo, una proprietà, ma non può.

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 può override il get-solo con get/set

Si desidera override con un get/set proprietà, ma non viene compilata.

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

Soluzione:Utilizzare un abstract strato intermedio

Mentre non è possibile direttamente override con un get/set la proprietà, si può:

  1. Creare un new get/set proprietà con lo stesso nome.

  2. override il vecchio get con il metodo di una funzione di accesso al nuovo get metodo per garantire la coerenza.

Quindi, prima di scrivere il abstract strato intermedio:

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, è possibile scrivere la classe non compilare in precedenza.Ti compilazione di questo tempo, perché non siete in realtà overrideing il getproprietà di sola lettura;invece, si sta sostituendo utilizzando il new parola chiave.

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 durante la visualizzazione di D come una delle sue classi base, ad esempio A o B.Ma, il motivo per cui funziona potrebbe essere un po ' meno ovvio.

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 in ultima analisi, ignorato dalla classe derivata definizione di get, quindi è ancora del tutto 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 set metodi per get-solo le proprietà.Si può anche usare per fare cose come questa:

  1. Modificare le proprietà in un get-solo, set-solo, o get-e-set proprietà, a prescindere di quello che era in una classe di base.

  2. Modificare il tipo di ritorno di un metodo in classi derivate.

I principali svantaggi sono che non c'è più la codifica da fare e un extra abstract class in eredità albero.Questo può essere un po ' fastidioso con i costruttori che accettano parametri a causa di quelli che devono essere copia/incollato nello strato intermedio.

Sono d'accordo che non essere in grado di eseguire l'override di un metodo getter in un tipo derivato è un anti-pattern.Di sola lettura specifica mancanza di attuazione, e non un contratto di un puro funzionale (implicita alto voto di risposta).

Ho il sospetto che Microsoft aveva questa limitazione sia perché lo stesso errore è stato promosso, o forse a causa di semplificazione grammaticale;anche se, ora che il campo di applicazione può essere applicato per ottenere o impostare individualmente, forse possiamo sperare di override può essere troppo.

L'equivoco indicato dal top voto rispondere, che una proprietà di sola lettura debba essere in qualche modo più "puro" di una proprietà di lettura/scrittura è ridicolo.Basta guardare a molti comuni di leggere solo le proprietà nel quadro;il valore non è costante / puramente funzionale;per esempio, DateTime.Ora è di sola lettura, ma qualcosa di più di un puro valore funzionale.Un tentativo di 'cache' un valore di una proprietà di sola lettura supponendo che restituisce lo stesso valore successivo è rischioso.

In ogni caso, ho utilizzato uno dei seguenti strategie per superare questa limitazione;entrambi sono meno che perfetto, ma vi permetterà di zoppicare al di là di questo linguaggio, deficit di:

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

Si potrebbe forse andare in giro per il problema con la creazione di una nuova struttura:

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

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

Posso capire tutti i tuoi punti, ma efficacemente, C# 3.0 proprietà automatico ottenere inutile in questo caso.

Non è possibile fare una cosa del genere:

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

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

Il problema è che per qualunque motivo Microsoft ha deciso che ci deve essere di tre tipi distinti di proprietà:di sola lettura, di scrittura e di lettura-scrittura, uno solo dei quali può esistere con un dato di firma in un dato contesto;proprietà può essere sottoposto a override da identico proprietà dichiarate.Per fare quello che vuoi, sarebbe necessario creare due proprietà con lo stesso nome e la firma-uno dei quali era di sola lettura, e di cui è stato letto-scrittura.

Personalmente, mi auguro che l'intero concetto di "proprietà" potrebbe essere abolito, tranne che di proprietà-ish sintassi potrebbe essere utilizzato come "zucchero sintattico" a chiamata "get" e "set" metodi.Questo non solo facilitare l' 'aggiungi set' opzione, ma permetterebbe anche di 'ottenere' per restituire un tipo di carattere diverso da 'set'.Mentre una tale capacità non essere utilizzato terribilmente spesso, potrebbe a volte essere utile avere un 'get' di ritorno del metodo di un oggetto wrapper, mentre il 'set' potrebbe accettare un wrapper o dati reali.

Qui è un work-around per ottenere questo utilizzando Riflessione:

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 siamo in grado di impostare il valore di oggetto su una proprietà che è in sola lettura.Potrebbe non funzionare in tutti gli scenari, però!

Si dovrebbe cambiare il titolo della domanda sia in dettaglio che la tua domanda è unico in quanto l'override di una proprietà astratta, o che la tua domanda riguarda in generale l'override di una classe di sola proprietà.


Se l'ex (l'override di una proprietà astratta)

Che codice è inutile.Una classe base da sola non dovrebbe dirti che sei costretto a ignorare una proprietà di sola (Forse un'Interfaccia).Una classe di base fornisce le funzionalità più comuni che possono richiedere specifici input da un implementazione di classe.Pertanto, le funzionalità comuni possono effettuare chiamate abstract proprietà o metodi.Nel caso di specie, la funzionalità comuni metodi dovrebbe essere chiedendo per voi per eseguire l'override di un metodo astratto come:

public int GetBar(){}

Ma se tu non hai il controllo, e la funzionalità di base di classe legge dalla propria proprietà pubblica (strano), allora basta fare 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 la (strana) commento:Direi una best practice è per una classe a non utilizzare il proprio pubblico di proprietà, ma per uso privato/i campi protetti quando esistono.Quindi, questo è migliore modello:

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 (override in una classe di proprietà di sola lettura)

Ogni oggetto non di proprietà astratta è un setter.Altrimenti è inutile, e che non dovresti usarlo.Microsoft non ha per permettere di fare quello che vuoi.Ragione di essere:il setter esiste in qualche forma o in altra, e si può realizzare ciò che si desidera Veerryy facilmente.

La classe di base, o di qualsiasi classe di cui si può leggere una proprietà con {get;}, ha ALCUNI sorta di esposto setter per quella proprietà.I metadati sarà simile a questa:

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

Ma l'attuazione avrà due estremità dello spettro di complessità:

Meno Complessi:

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 è, in ogni modo, hai il setter esposti.Nel tuo caso, è possibile ignorare int Bar perché non vuole che la classe di base per gestire, non hanno accesso ad esaminare in che modo la gestione, o avevano il compito di hax codice rapido reale n'piccolo contro la tua volontà.

In entrambi questi Ultimi e l'Ex (Conclusione)

Lunga Storia Breve:Non è necessario per Microsoft di cambiare nulla.Puoi scegliere la modalità di attuazione di classe è costituito e, senza il costruttore, l'utilizzo di tutti o di nessuno della classe base.

Soluzione per solo un piccolo sottoinsieme di casi d'uso, ma comunque:in C# 6.0 "readonly" setter viene automaticamente aggiunto per l'override dei metodi getter proprietà.

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

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

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

Perché il livello, una proprietà di lettura/scrittura si traduce in due (getter e setter) metodi.

Quando si esegue l'override, è necessario mantenere il supporto sottostante interfaccia.Se si potesse aggiungere un setter, si sarebbe effettivamente l'aggiunta di un nuovo metodo, che restano invisibili al mondo esterno, per quanto riguarda le classi' interfaccia in questione.

Vero, l'aggiunta di un nuovo metodo di non rompere la compatibilità di per sé, ma in quanto rimarrebbero nascosti, decisione di non consentire questo ha il senso perfetto.

Perché sarebbe rompere il concetto di incapsulamento e di attuazione nascondere.Consideriamo il caso in cui si crea una classe, la nave, e quindi il consumatore di classe rende in grado di impostare una proprietà per cui è stato originariamente fornire un getter solo.Effettivamente si sarebbe disturbare le invarianti di classe che si può dipendere la vostra applicazione.

Perché una classe che ha una proprietà di sola lettura (non setter), probabilmente ha una buona ragione per farlo.Non ci può essere alcun archivio dati sottostante, per esempio.Consente di creare un setter rompe il contratto previsti dalla classe.E ' solo un cattivo OOP.

Una proprietà di sola lettura in classe di base indica che questa struttura rappresenta un valore che può essere determinata dall'interno della classe (ad esempio un valore di enumerazione di corrispondenza l' (db-a)contesto di un oggetto).Così il responsibillity di determinare il valore rimane all'interno della classe.

L'aggiunta di un setter di un imbarazzante problema qui:Un errore di convalida dovrebbe verificarsi se si imposta il valore a qualcosa di altro rispetto al possibile valore che già ha.

Le regole spesso sono le eccezioni, però.E ' possibile che, per esempio, in una classe derivata contesto restringe i possibili valori enum giù per 3 delle 10, ma l'utente di questo oggetto deve ancora decidere quale delle due è quella corretta.La classe derivata deve delegare la responsibillity di determinare il valore per l'utente di questo oggetto.Importante rendersi conto è che l'utente di questo oggetto dobbiamo essere ben consapevoli di questa eccezione e si supponga che il responsibillity per impostare il valore corretto.

La mia soluzione a questo tipo di situazioni, sarebbe quello di lasciare la proprietà di sola lettura e aggiungere una nuova proprietà di lettura / scrittura per la classe derivata per sostenere l'eccezione.L'override della proprietà originale semplicemente restituire il valore della nuova proprietà.La nuova proprietà può avere un nome proprio, che indica il contesto di questa eccezione correttamente.

Questo supporta anche la valida osservazione:"rendere più forte possibile per incomprensioni per ritagliare", da Gishu.

Questo non è impossibile.Basta utilizzare la parola chiave "new" nella vostra proprietà.Per esempio,

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

Sembra come se questo approccio soddisfa entrambi i lati di questa discussione.L'utilizzo di "nuovo" si rompe il contratto tra l'implementazione della classe base e la sottoclasse di attuazione.Questo è necessario quando una Classe può avere più contratti (sia tramite interfaccia o classe base).

Spero che questo aiuta

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top