Campo protetto di sola lettura vs proprietà protetta

Ho una class astratta e mi piacerebbe inizializzare un campo readonly nel suo costruttore protetto. Mi piacerebbe che questo campo di sola lettura fosse disponibile nelle classi derivate.

Seguendo la mia abitudine di rendere tutti i campi privati ​​e di mostrare le proprietà, l’ho implementato come segue:

abstract class Foo { private readonly int _field; protected Foo(int field) { _field = field; } protected int Field { get { return _field; } } } 

Ma poi mi sono chiesto se c’è davvero molto vantaggio nel mantenere il campo privato qui. Sono consapevole dei vantaggi delle proprietà e ci sono diverse domande SO su questo argomento in generale, ma si concentrano su campi pubblici piuttosto che su quelli protetti.

Quindi dovrei passare alla sotto implementazione o no? Quali sono le considerazioni e i vantaggi / svantaggi di cui essere a conoscenza in entrambi i casi?

 abstract class Foo { protected readonly int _field; protected Foo(int field) { _field = field; } } 

Le classi derivate sono ancora “utenti” del codice originale; i campi dovrebbero essere incapsulati anche da loro.

Dovresti pensare alle classi di base come API sicure ed estendibili, piuttosto che solo classi che espongono i loro interni. Mantieni i campi privati ​​- a prescindere da qualsiasi cosa, consente alla class base di cambiare il modo in cui viene generato il valore di quella proprietà 🙂

Lascio l’implementazione o vado con:

 protected Field {get; private set;} 

(non esattamente lo stesso di Field non è di sola lettura per la class genitore)

Il vantaggio delle proprietà sui campi è che sono più a prova di futuro. È ansible modificare l’implementazione sulla class genitore senza influenzare i bambini. Senza la proprietà che ti impegni nel campo, sarai sempre in sola lettura. L’uso dei campi è disapprovato per un’altra ragione, fanno male all’opacità: la class descrive l’esatta implementazione del campo.

Quindi sì, lascia l’incapsulamento.

L’unico posto in cui prenderei in considerazione l’utilizzo diretto dei campi per la leggibilità è in classi altamente accoppiate come gli stati in uno schema di stato, ma in questo caso le classi stesse sarebbero private.

Le proprietà di sola lettura sono state implementate in C # 6.0. Questo è semplice come:

 protected Foo(int field) { Field = field; } protected int Field { get; } 
 abstract class Foo { protected readonly int _field; protected Foo(int field) { _field = field; } } 

Sarà appropriato come vuoi che la class derivata ne sia a conoscenza. Classi non di tipo Foo non avranno accesso a _field .

Usando il riflesso puoi sovrascrivere facilmente il campo di sola lettura. L’utilizzo di una proprietà rende più difficile perché il campo è nascosto (puoi ancora farlo). Quindi preferirei una proprietà che è comunque la più pulita perché puoi cambiare il getter senza problemi.

Se stai pensando alle prestazioni: le proprietà sono inline maggior parte delle volte.

Override di readonly

 class Program { static void Main(string[] args) { Test t = new Test(); t.OverrideReadonly("TestField", 5); t.OverrideReadonly("TestField2", 6); t.OverrideReadonly("TestField3", new Test()); } } class Test { protected readonly Int32 TestField = 1; protected readonly Int32 TestField2 = 2; protected readonly Test TestField3 = null; public void OverrideReadonly(String fieldName, Object value) { FieldInfo field = typeof(Test).GetField(fieldName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); field.SetValue(this, value); } } 

Posso pensare a due motivi per preferire una proprietà protetta su un campo protetto. Innanzitutto, considera questo esempio:

 public class BaseClass { protected readonly List _someValues; } public class InheritedClass : BaseClass { public void NeedsThoseValues() { DoSomethingWith(_someValues); } } 

Ora decidi di cambiare la class base per usare l’istanza lazy:

 public class BaseClass { protected readonly Lazy> _someValues; } 

Ora le classi ereditate devono cambiare per chiamare _someValues.Value . Le classi ereditate avevano davvero bisogno di cambiare? Se il campo era privato ed esposto alle classi ereditate come una proprietà, la modifica della class base non romperebbe le classi ereditate:

 public class BaseClass { private readonly Lazy> _someValues; protected List SomeValues => _someValues.Value; } 

Questo ci impone di cambiare il modo in cui immaginiamo mentalmente le classi ereditate. Potremmo iniziare a visualizzarlo come build una casa più grande intorno a una casa più piccola. Tutto ciò che era nella casa più piccola è nella casa più grande, quindi è davvero tutta una grande casa. Non c’è motivo di hide la casa interna dalla casa esterna.

In realtà non è niente del genere. La class base è la sua entity framework distinta che esiste all’interno della casa più grande. Per riutilizzarlo in più case (classi ereditate multiple) dobbiamo incapsularlo in modo che le classi ereditate non sappiano più di quanto ne abbiano bisogno, proprio come con qualsiasi altra class da cui dipendono.

Questo a volte crea una strana relazione. Stiamo cercando di evitare un eccessivo accoppiamento tra la class ereditata e i dettagli di implementazione della class base, eppure sono accoppiati, perché la class ereditata non può esistere senza la class base. Ciò solleva la domanda: perché la class ereditaria dovrebbe avere questa relazione con la class base? Perché non separare ciò che la class base fa nella propria class, rappresentarla con un’astrazione (come un’interfaccia) e iniettare ciò che è necessario?

Questa è l’idea alla base della preferenza per la composizione sull’ereditarietà. Molte volte l’ereditarietà viene utilizzata per condividere la funzionalità tra le classi (la base e il figlio) quando non è quello a cui serve. Se abbiamo due diverse aree di funzionalità, dovremmo farlo con due classi diverse, e una può dipendere dall’altra. Se riusciremo a raggiungere questo objective attraverso l’ereditarietà, avremo un problema quando la nostra class deve dipendere da un’altra class. Non possiamo dargli un’altra class base. A meno che non stiamo componendo classi distinte per lavorare insieme, potremmo iniziare a fare cose davvero malvagie, come aggiungere più funzionalità alle classi base esistenti o creare più livelli di ereditarietà. Diventa difficile da capire e alla fine ci dipinge in un angolo.

L’altro motivo è che quando qualcuno vede _someValues cui _someValues riferimento nella class base, assumerà che si tratta di un campo dichiarato nella class in cui viene utilizzato poiché è la convenzione più comune. Non creerà alcuna enorme confusione, ma ci vorrà un po ‘più di tempo per capirlo.

Questi motivi per preferire le proprietà readonly protette su campi protetti di sola lettura potrebbero non essere un problema in molti casi particolari. Ma è difficile saperlo in anticipo, quindi è una buona idea scegliere la pratica preferita e solo prendere un’abitudine, comprendendo il motivo per cui lo stai facendo.

L’utilizzo di un campo privato e di un getter protetto ha un leggero vantaggio: non si causa un ulteriore livello di riferimento indiretto.

Non dimenticare il modo c # di stenografia per dichiarare le proprietà:

 protected int Field { protected get; private set; }