Perché è impossibile ignorare una proprietà solo getter e aggiungere un setter? [chiuso]


141

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 accessor sostituibile


9
StackOverflow ha una capacità limitata di rispondere alle domande per conto di Microsoft. Valuta di riformulare la tua domanda.
Jay Bazuzi,

1
Per inciso, un semplice rimedio per questo fastidioso comportamento in .net è avere una proprietà di sola lettura non virtuale Fooche non fa altro che avvolgere un GetFoometodo astratto protetto . Un tipo derivato astratto può ombreggiare la proprietà con una proprietà di lettura-scrittura non virtuale che non fa altro che avvolgere il GetFoometodo sopra citato con un SetFoometodo astratto ; un tipo derivato concreto potrebbe fare quanto sopra ma fornire definizioni per GetFooe SetFoo.
supercat

1
Solo per aggiungere carburante al fuoco, C ++ / CLI lo consente.
ymett,

1
La possibilità di aggiungere un accessorio durante l'override di una proprietà è stata proposta come modifica per una versione futura di C # ( github.com/dotnet/roslyn/issues/9482 ).
Brandon Bonds,

1
Ad essere sinceri, questa domanda viene spesso sollevata quando è necessario estendere un tipo di libreria esistente che utilizza il seguente modello: classe base di sola lettura, classe mutabile derivata. Poiché la libreria utilizza già un modello errato (come WPF), è necessario risolvere questo modello errato. Le lezioni non evitano di dover attuare una soluzione alternativa alla fine della giornata.
dal

Risposte:


-4

Perché lo scrittore di Baseclass ha esplicitamente dichiarato che Bar deve essere una proprietà di sola lettura. Non ha senso che le derivazioni rompano questo contratto e lo rendono lettura-scrittura.

Sono con Microsoft su questo.
Diciamo che sono un nuovo programmatore a cui è stato detto di codificare contro la derivazione di Baseclass. scrivo qualcosa che presuppone che Bar non possa essere scritto (poiché la Baseclass afferma esplicitamente che si tratta di 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 presuppone che la memorizzazione nella cache sia una cosa sicura da fare, poiché Bar non può essere modificato. Ma se set era possibile in una derivazione, questa classe potrebbe servire valori non aggiornati se qualcuno modificasse esternamente la proprietà Bar dell'oggetto _source. Il punto è " Sii aperto, evita di fare cose subdole e sorprendi le persone "

Aggiornamento : Ilya Ryzhenkov chiede "Perché allora le interfacce non giocano secondo le stesse regole?" Hmm .. questo diventa più confuso mentre ci penso.
Un'interfaccia è un contratto che dice "aspettati che un'implementazione abbia una proprietà di lettura denominata Bar". Personalmente sono molto meno propenso a fare quell'ipotesi di sola lettura se vedessi un'interfaccia. Quando vedo una proprietà di sola lettura su un'interfaccia, la leggo come "Qualsiasi implementazione esporrebbe questa barra degli attributi" ... su una classe base fa clic su "La barra è una proprietà di sola lettura". Naturalmente tecnicamente non stai rompendo il contratto .. stai facendo di più. Quindi, in un certo senso, hai ragione. Vorrei chiudere dicendo "rendere il più difficile possibile il sorgere di incomprensioni".


94
Non sono ancora convinto. Anche se non esiste un settatore esplicito, ciò non garantisce che la proprietà restituirà sempre lo stesso oggetto: può comunque essere modificata con un altro metodo.
ripper234,

34
Lo stesso non dovrebbe essere vero per le interfacce, allora? È possibile avere la proprietà readonly sull'interfaccia e leggere / scrivere sul tipo di implementazione.
Ilya Ryzhenkov,

49
Non sono d'accordo: la classe base ha dichiarato che la proprietà non ha setter; non è lo stesso della proprietà di sola lettura. "Sola lettura" è solo un significato che gli viene assegnato. Non ci sono garanzie rilevanti integrate in C #. Potresti avere una proprietà get-only che cambia ogni volta o una proprietà get / set in cui il setter lancia un InvalidOperationException("This instance is read-only").
Roman Starkov,

21
Considerando metodi e setter come metodi, non vedo perché dovrebbe essere una "violazione del contratto" più che aggiungere un nuovo metodo. Un contratto di classe base non ha nulla a che fare con ciò che la funzionalità non è presente, solo con ciò che è presente.
Dave Cousineau,

37
-1 Non sono d'accordo con questa risposta e la trovo fuorviante. Poiché una classe base definisce il comportamento a cui qualsiasi classe deve attenersi (vedi il Principio di sostituzione di Liskov), ma non (e non dovrebbe) limitarsi ad aggiungere comportamento. Una classe di base può solo definire quale deve essere il comportamento e non può (e non dovrebbe) specificare quale comportamento 'non deve essere' (cioè 'non deve essere scrivibile a livelli concreti)'.
Marcel Valdez Orozco,

39

Penso che il motivo principale sia semplicemente che la sintassi è troppo esplicita perché funzioni in qualsiasi altro modo. Questo codice:

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

è abbastanza esplicito che sia il sia getil setsono override. Non ce n'è setnella classe base, quindi il compilatore si lamenta. Proprio come non puoi sovrascrivere un metodo che non è definito nella classe base, non puoi nemmeno scavalcare un setter.

Potresti dire che il compilatore dovrebbe indovinare la tua intenzione e applicare l'override solo al metodo che può essere ignorato (ovvero 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é potrebbe indovinare sbagliato senza che tu lo sappia.

Penso che la seguente sintassi potrebbe fare bene, ma come dice Eric Lippert, implementare anche una funzione minore come questa è ancora un grande sforzo ...

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

o, per le proprietà autoimplementate,

public int MyProperty { override get; set; }

19

Oggi mi sono imbattuto nello stesso problema e penso che abbia un motivo molto valido per volerlo.

Per prima cosa vorrei sostenere che avere una proprietà get-only non si traduce necessariamente in sola lettura. Lo interpreto come "Da questa interfaccia / abtract puoi ottenere questo valore", ciò non significa che alcune implementazioni di quella interfaccia / classe astratta non avranno bisogno dell'utente / programma per impostare esplicitamente questo valore. Le classi astratte servono allo scopo di implementare parte della funzionalità necessaria. 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. La ragione per aggiungere il setter e non aggiungere, per esempio, un metodo SetProp è che una particolare implementazione dell'interfaccia utilizzava DataContract / DataMember per la serializzazione di Prop, che sarebbe stato reso inutilmente complicato se avessi dovuto aggiungere un'altra proprietà solo allo 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 "i miei 2 centesimi", ma mi sento con il poster originale e cercare di razionalizzare che questa è una buona cosa mi sembra strano, soprattutto considerando che le stesse limitazioni non si applicano quando si eredita direttamente da un interfaccia.

Anche la menzione sull'uso di new anziché override 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.


2
O, nel mio caso, {get; set privato; }. Non sto violando alcun contratto poiché il set rimane privato e riguarda solo la classe derivata.
Machtyn,

16

È possibile

tl; dr - Se lo desideri, puoi sovrascrivere un metodo get-only con un setter. Fondamentalmente è solo:

  1. Creare una newproprietà con sia a getche a setusando lo stesso nome.

  2. Se non fai altro, il vecchio getmetodo verrà comunque chiamato quando la classe derivata viene chiamata attraverso il suo tipo base. Per risolvere questo problema, aggiungi un abstractlivello intermedio che utilizza overrideil vecchio getmetodo per forzarlo a restituire il getrisultato del nuovo metodo.

Questo ci consente di sovrascrivere le proprietà con get/ setanche se ne mancava una nella loro definizione di base.

Come bonus, puoi anche cambiare il tipo di ritorno, se lo desideri.

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

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

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

    • puoi usare un tipo restituito più derivato se lo rendi set-solo;

    • puoi usare un tipo restituito meno derivato se lo rendi get-solo.

In tutti i casi, è possibile mantenere lo stesso tipo di restituzione, se lo si desidera. Gli esempi seguenti utilizzano lo stesso tipo restituito per semplicità.

Situazione: getproprietà solo esistente

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 setmetodo 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: Impossibile overrideil get-Solo con get/set

Si desidera overridecon 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 uno abstractstrato intermedio

Anche se non puoi direttamente overridecon una proprietà get/ set, puoi :

  1. Crea una proprietà new get/ setcon lo stesso nome.

  2. overrideil vecchio getmetodo con un accessor al nuovo getmetodo per garantire coerenza.

Quindi, prima scrivi il abstractlivello 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 scrivi la classe che non si sarebbe compilata prima. Sarà compilazione questa volta perché si sta in realtà non override'ing l' getimmobile -Solo; invece, lo stai sostituendo usando la newparola 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 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 come previsto quando viene visualizzato Dcome una delle sue classi di base, ad esempio Ao 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 alla fine sostituita dalla definizione della classe derivata di 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 setmetodi alle getsole proprietà. Puoi anche usarlo per fare cose come:

  1. Cambia qualsiasi proprietà in una get-only, set-only o get-and- setproprietà, indipendentemente da ciò che era in una classe base.

  2. Modifica il tipo restituito di un metodo nelle classi derivate.

Gli svantaggi principali sono che c'è più codice da fare e un extra abstract classnella struttura ereditaria. Questo può essere un po 'fastidioso con i costruttori che accettano parametri perché devono essere copiati / incollati nel livello intermedio.


Ha dato questa domanda: stackoverflow.com/questions/22210971
Nat

Approccio piacevole (+1). Dubito tuttavia che funzionerà senza problemi con il framework delle entità.
Mike de Klerk,

9

Concordo sul fatto che non essere in grado di ignorare un getter in un tipo derivato è un anti-pattern. Sola lettura specifica la mancanza di implementazione, non un contratto di un puro funzionale (implicito dalla risposta del voto più alto).

Sospetto che Microsoft avesse questa limitazione o perché era stato promosso lo stesso malinteso, o forse a causa della semplificazione della grammatica; tuttavia, ora che l'ambito può essere applicato per ottenere o impostare individualmente, forse possiamo sperare che anche l'override possa esserlo.

L'idea sbagliata indicata dalla risposta più votata, secondo cui 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 è una costante / puramente funzionale; ad esempio, DateTime.Now è di sola lettura, ma tutt'altro che un valore funzionale puro. Un tentativo di "memorizzare nella cache" un valore di una proprietà di sola lettura presupponendo che restituisca 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 andare oltre questo deficit linguistico:

   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 è puramente funzionale, in quanto non ha effetti collaterali (il fatto che ci vuole tempo per eseguire potrebbe essere plausibilmente chiamato un effetto collaterale, ma dal momento che il tempo di esecuzione - almeno quando limitato e breve - non è considerato un effetto collaterale di qualsiasi altro metodo, non lo considero neanche uno qui).
supercat

1
Ciao supercat. Hai ragione sugli effetti collaterali osservabili esternamente che impediscono che una funzione sia pura, ma l'altro vincolo è che un valore di risultato di funzione pura deve dipendere solo dai parametri. Una proprietà statica pura, come funzione, deve quindi essere sia immutabile che costante. Un compilatore sofisticato può sostituire tutte le chiamate successive in una funzione pura senza parametri con il risultato restituito dalla prima chiamata.
T.Tobler

Hai ragione nel dire che la mia terminologia era imprecisa, ma penso che l'adeguatezza di qualcosa che sia una proprietà di sola lettura piuttosto che un metodo dipenda dalla mancanza di effetti collaterali, piuttosto che dalla purezza.
supercat

2

Potresti forse aggirare il problema creando una nuova proprietà:

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

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

4
Ma puoi farlo solo quando implementi un'interfaccia, non quando erediti da una classe base. Dovresti scegliere un altro nome.
svick,

Suppongo che la classe di esempio implementa IBase (qualcosa come l'interfaccia pubblica IBase {int Bar {get;}}), altrimenti nella classe di implementazione, int IBase.Bar {...} non è permesso. Quindi puoi semplicemente creare una normale barra delle proprietà con get e set, anche se la proprietà Bar dall'interfaccia non richiede un set.
Ibrahim ben Salah,

1

Riesco a capire tutti i tuoi punti, ma in effetti le proprietà automatiche di 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.


Non capisco il tuo punto. Sei d'accordo sul fatto che C # dovrebbe consentire l'aggiunta di un setter o sei con la maggior parte delle altre persone che hanno risposto a questa domanda?
ripper234,

2
Come ho detto, IMO, C # dovrebbe consentire l'aggiunta di un setter. È responsabilità dello sviluppatore utilizzarlo di conseguenza.
Thomas Danecker,

1

Il problema è che per qualsiasi motivo Microsoft ha deciso che dovrebbero esistere tre tipi distinti di proprietà: sola lettura, sola scrittura e lettura-scrittura, solo una delle quali può esistere con una data firma in un determinato contesto; le proprietà possono essere sovrascritte solo da proprietà dichiarate in modo identico. Per fare ciò che desideri sarebbe necessario creare due proprietà con lo stesso nome e la stessa firma - una delle quali era di sola lettura e una era di lettura-scrittura.

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


0

Ecco una soluzione per raggiungere questo obiettivo tramite 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à di sola lettura. Potrebbe non funzionare in tutti gli scenari però!


1
Sei sicuro di poter chiamare un setter tramite la riflessione che non esiste (come accade nelle proprietà di solo getter descritte)?
OR Mapper,

0

È necessario modificare il titolo della domanda in modo da specificare che la domanda riguarda esclusivamente l'override di una proprietà astratta o che la domanda riguarda l'override generale della proprietà get-only di una classe.


Se il primo (sovrascrivendo una proprietà astratta)

Quel codice è inutile. Una sola classe base non dovrebbe dirti che sei costretto a sovrascrivere una proprietà Get-Only (forse un'interfaccia). Una classe di base fornisce funzionalità comuni che potrebbero 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 sostituire 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 (strana), 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 buona pratica è che una classe non usi le sue proprietà pubbliche, ma 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 permetterti di fare quello che vuoi. Il motivo è: il setter esiste in una forma o nell'altra e puoi realizzare facilmente ciò che desideri Veerryy .

La classe base, o qualsiasi classe in cui è possibile leggere una proprietà {get;}, ha ALCUNI tipo di setter esposto per quella proprietà. I metadati saranno così:

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

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

Il meno complesso:

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

In ogni caso, il punto è che hai esposto il setter. Nel tuo caso, potresti voler eseguire l'override int Barperché non vuoi che la classe base la gestisca, non hai accesso per rivedere come la sta gestendo, o ti è stato assegnato il compito di scrivere un codice molto veloce contro la tua volontà .

In entrambi gli ultimi e gli ex (conclusione)

Per farla breve: non è necessario che Microsoft cambi nulla. Puoi scegliere come impostare la tua classe di implementazione e, senza il costruttore, usare tutta o nessuna della classe di base.


0

Soluzione solo per un piccolo sottoinsieme di casi d'uso, ma tuttavia: in C # 6.0 il setter "sola lettura" viene aggiunto automaticamente per le proprietà di 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;
    }
}

-1

Perché ciò spezzerebbe il concetto di incapsulamento e nascondere l'implementazione. Considera il caso quando crei una classe, la spedisci e quindi il consumatore della tua classe si rende in grado di impostare una proprietà per la quale in origine fornisci solo un getter. Distruggerebbe efficacemente qualsiasi invariante della tua classe da cui puoi fare affidamento nella tua implementazione.


1
-1: Avere un setter non abilita automaticamente un discendente a infrangere alcun invariante. Il setter può comunque cambiare solo cose che sono già modificabili dal discendente.
Roman Starkov,

@RomanStarkov È vero. Ma la domanda di questo post è; Perché non riesco ad aggiungere un setter a un'astrazione a cui non ti impegni. Il tuo commento sarebbe appropriato nella domanda opposta; Perché Microsoft ti consente di creare una proprietà ReadOnly in un'astrazione se il discendente ha comunque il pieno controllo dei suoi campi privati?
Suamere,

+1: questa domanda riguarda il motivo per cui Microsoft non consente di interrompere un contratto. Sebbene il motivo dei tuoi downvotes sia più legato al motivo per cui Microsoft consente a un contratto di includere solo proprietà di sola lettura. Quindi questa risposta è corretta nel contesto.
Suamere,

@Suamere il contratto non afferma che la proprietà non può essere modificata. Tutto ciò che afferma è che esiste una proprietà che può essere letta. L'aggiunta di un setter non interromperà questo contratto. Puoi interpretarlo come un'intenzione di creare una proprietà di sola lettura, ma non puoi garantire in C # 6 che non c'è modo di cambiarlo, quindi il contratto a cui stai pensando non è in realtà lì. Pensa a un'interfaccia: può offrire un contratto su una proprietà ottenibile (non solo get!). Questa interfaccia può essere implementata da una proprietà get + set bene.
Roman Starkov,

Ti stai avvicinando dal punto di vista dell'autore concreto. Può cambiarlo come vuole. Mi sto avvicinando dal punto di vista dell'autore della classe base -> Mi sto avvicinando come consumatore finale della classe attuatrice. L'autore della classe base non vuole che nessun consumatore delle future classi concrete possa cambiare quella proprietà senza che l'autore della classe concreta esponga un metodo speciale per gestire caso per caso. Perché nella classe base, quella proprietà viene considerata immutabile e viene comunicata solo esponendola come tale.
Suamere,

-1

Questo non è impossibile. Devi semplicemente usare la "nuova" parola chiave nella tua 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 che questo approccio soddisfi entrambi i lati di questa discussione. L'uso di "nuovo" interrompe il contratto tra l'implementazione della classe base e l'implementazione della sottoclasse. Ciò è necessario quando una Classe può avere più contratti (tramite interfaccia o classe base).

Spero che questo ti aiuti


29
Questa risposta è errata perché la proprietà contrassegnata newnon sostituisce più quella di base virtuale. In particolare, se la proprietà di base è astratta , come nel post originale, è impossibile utilizzare la newparola chiave in una sottoclasse non astratta poiché è necessario sovrascrivere tutti i membri astratti.
Timwi,

Ciò è straordinariamente pericoloso perché i seguenti risultati restituiscono risultati diversi nonostante siano lo stesso oggetto: Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)ti dà 5, Console.WriteLine(b.BaseProperty)ti dà 0 e quando il tuo successore deve eseguire il debug, è meglio che tu ti sia spostato molto, molto lontano.
Mark Sowul,

Sono d'accordo con Mark, questo è estremamente brutto. Se entrambe le implementazioni di BaseProperty fossero supportate dalla stessa variabile, andrebbe bene anche se IMHO. IE: Rendi _baseProperty protetto e abolisci _testBaseProperty. Inoltre, AFAIK l'implementazione in Base non deve essere virtuale poiché in realtà non la sovrascrivi.
Steazy,

La maggior parte delle risposte alle domande può avere opinioni e input validi. Ma a parte i commenti precedenti, la mia critica principale a questa risposta è che credo che la vera domanda possa essere letteralmente sulle proprietà astratte in una base di codice su cui il consumatore non ha alcun controllo. Quindi la mia critica è che questa risposta potrebbe non adattarsi alla domanda.
Suamere,

-1

Una proprietà di sola lettura nella classe base indica che questa proprietà rappresenta un valore che può sempre essere determinato all'interno della classe (ad esempio un valore enum corrispondente al contesto (db-) di un oggetto). Quindi la responsabilità di determinare il valore rimane all'interno della classe.

L'aggiunta di un setter causerebbe qui un problema imbarazzante: un errore di convalida dovrebbe verificarsi se si imposta il valore su un valore diverso dal singolo valore possibile che già possiede.

Le regole spesso hanno delle eccezioni, comunque. È molto probabile che, ad esempio, in una classe derivata il contesto restringa i possibili valori di enum fino a 3 su 10, ma l'utente di questo oggetto deve ancora decidere quale è corretta. La classe derivata deve delegare la responsabilità di determinare il valore per l'utente di questo oggetto. È importante rendersi conto che l'utente di questo oggetto dovrebbe essere ben consapevole di questa eccezione e assumere la responsabilità di impostare il valore corretto.

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

Ciò supporta anche l'osservazione valida: "rendere il più difficile possibile il sorgere di incomprensioni" di Gishu.


1
Nessuna imbarazzo sarebbe implicita da una ReadableMatrixclasse (con una proprietà indicizzata di sola lettura a due argomenti) con sottotipi ImmutableMatrixe MutableMatrix. Il codice che deve solo leggere una matrice e non si preoccupa se potrebbe cambiare un po 'di tempo in futuro, potrebbe accettare un parametro di tipo ReadableMatrixed essere perfettamente soddisfatto dei sottotipi mutabili o immutabili.
supercat

Un setter aggiuntivo potrebbe modificare una variabile privata all'interno della classe. La cosiddetta proprietà "sola lettura" della classe base determinerebbe quindi il valore "dall'interno della classe" (leggendo la nuova variabile privata). Non vedo il problema lì. Guarda anche, ad esempio, cosa List<T>.Countfa: non può essere assegnato a, ma ciò non significa che sia "di sola lettura" in quanto non può essere modificato dagli utenti della classe. Può benissimo essere modificato, anche se indirettamente, aggiungendo e rimuovendo gli elementi dell'elenco.
OR Mapper,

-2

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

Quando si esegue l'override, è necessario continuare a supportare l'interfaccia sottostante. Se potessi aggiungere un setter, aggiungeresti effettivamente un nuovo metodo, che rimarrebbe invisibile al mondo esterno, per quanto riguarda l'interfaccia delle tue classi.

È vero, l'aggiunta di un nuovo metodo non comprometterebbe la compatibilità di per sé, ma poiché rimarrebbe nascosta, la decisione di non consentire ciò ha perfettamente senso.


2
Non mi interessa molto del "mondo esterno" qui. Voglio aggiungere funzionalità a una classe, che è sempre visibile solo al codice che conosce la classe specifica e non la base.
ripper234,

@ ripper234 Vedi la risposta di Matt bolt: se vuoi rendere la nuova proprietà visibile solo agli utenti della classe derivata, dovresti usare newinvece di override.
Dan Berindei,

1
Non rimane nascosto. Stai dicendo che non puoi aggiungere un nuovo metodo a una classe figlio perché il nuovo metodo "rimarrà nascosto al mondo esterno". Naturalmente rimane nascosto dalla prospettiva della classe base, ma è accessibile dai clienti della classe figlio. (Vale a dire che cosa dovrebbe restituire il getter)
Sheepy,

Sheepy, non è affatto quello che sto dicendo. Voglio dire, esattamente come hai sottolineato, rimarrebbe nascosto "da una prospettiva di classe base". Ecco perché ho aggiunto "per quanto riguarda l' interfaccia delle tue classi ". Se stai eseguendo la sottoclasse di una classe base, è quasi certo che il "mondo esterno" si riferirà alle tue istanze di oggetto con il tipo di classe base. Se utilizzassero direttamente la tua classe concreta, non avresti bisogno della compatibilità con la classe base e potresti, in ogni caso, usarla newcon le tue aggiunte.
Ismaele

-2

Perché una classe che ha una proprietà di sola lettura (nessun setter) probabilmente ha una buona ragione per questo. Ad esempio, potrebbe non esserci alcun archivio dati sottostante. Consentire di creare un setter rompe il contratto stabilito dalla classe. È solo brutto OOP.


Up-Voto. Questo è molto succinto. Perché Microsoft dovrebbe consentire al consumatore di una classe base di rompere il contratto? Sono d'accordo con questa versione molto breve della mia risposta troppo lunga e confusa. Lol. Se sei un consumatore di una classe base, non dovresti voler rompere il contratto, ma PUOI implementarlo in un paio di modi diversi.
Suamere,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.