¿Por qué es imposible anular una propiedad getter y agregar un setter?

¿Por qué crees (o, por qué es bueno que) Microsoft decidió no permitir:

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’: no ​​puede anular porque ‘BaseClass.Bar’ no tiene un acceso de conjunto invalidable

Porque el escritor de Baseclass ha declarado explícitamente que Bar tiene que ser una propiedad de solo lectura. No tiene sentido que las derivaciones rompan este contrato y lo hagan de lectura-escritura.

Estoy con Microsoft en este caso.
Digamos que soy un nuevo progtwigdor al que se le ha pedido que codifique contra la derivación de Baseclass. Escribo algo que supone que no se puede escribir en Bar (ya que Baseclass declara explícitamente que es una propiedad de solo obtener). Ahora con su derivación, mi código puede romperse. p.ej

 public class BarProvider { BaseClass _source; Bar _currentBar; public void setSource(BaseClass b) { _source = b; _currentBar = b.Bar; } public Bar getBar() { return _currentBar; } } 

Como Bar no se puede configurar según la interfaz BaseClass, BarProvider supone que el almacenamiento en caché es una tarea segura, ya que Bar no se puede modificar. Pero si el conjunto fuera posible en una derivación, esta clase podría estar sirviendo a valores obsoletos si alguien modificara la propiedad Bar del objeto _source externamente. El punto es ‘ Sé abierto, evita hacer cosas furtivas y sorprender a la gente

Actualización : Ilya Ryzhenkov pregunta ‘¿Por qué las interfaces no juegan con las mismas reglas entonces?’ Hmm … esto se pone más fangoso cuando lo pienso.
Una interfaz es un contrato que dice ‘esperar que una implementación tenga una propiedad de lectura llamada Bar’. Personalmente , es mucho menos probable que haga esa suposición de solo lectura si veo una interfaz. Cuando veo una propiedad de solo obtener acceso en una interfaz, la leo como ‘Cualquier implementación expone este atributo Bar’ … en una clase base, hace clic en ‘Bar es una propiedad de solo lectura’. Por supuesto, técnicamente no estás rompiendo el contrato … estás haciendo más. Así que tienes razón en cierto sentido. Cerraría diciendo ‘haz que sea lo más difícil posible que surjan malentendidos’.

Creo que la razón principal es simplemente que la syntax es demasiado explícita para que esto funcione de otra manera. Este código:

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

es bastante explícito que tanto el get como el set son anulaciones. No hay ningún set en la clase base, por lo que el comstackdor se queja. Al igual que no puede anular un método que no está definido en la clase base, tampoco puede anular a un colocador.

Podría decir que el comstackdor debe adivinar su intención y solo aplicar la anulación al método que puede anularse (es decir, el captador en este caso), pero esto va en contra de uno de los principios de diseño de C #: que el comstackdor no debe adivinar sus intenciones. , porque puede adivinar mal sin que lo sepas.

Creo que la siguiente syntax podría funcionar muy bien, pero, como Eric Lippert sigue diciendo, implementar incluso una característica menor como esta sigue siendo una gran cantidad de esfuerzo …

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

o, para propiedades autoejecutables,

 public int MyProperty { override get; set; } 

Hoy me encontré con el mismo problema y creo que tengo una razón muy válida para querer esto.

Primero, me gustaría argumentar que tener una propiedad para obtener solo no necesariamente se traduce en solo lectura. Lo interpreto como “De esta interfaz / abtract puedes obtener este valor”, eso no significa que alguna implementación de esa interfaz / clase abstracta no necesite que el usuario / progtwig establezca este valor explícitamente. Las clases abstractas sirven para implementar parte de la funcionalidad necesaria. No veo absolutamente ninguna razón por la cual una clase heredada no pueda agregar un colocador sin violar ningún contrato.

El siguiente es un ejemplo simplificado de lo que necesitaba hoy. Terminé teniendo que agregar un setter en mi interfaz solo para evitar esto. La razón para agregar el setter y no agregar, por ejemplo, un método SetProp es que una implementación particular de la interfaz usó DataContract / DataMember para la serialización de Prop, lo que se hubiera hecho innecesariamente complicado si tuviera que agregar otra propiedad solo para este propósito. de serialización.

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

Sé que esta es solo una publicación de “mis 2 centavos”, pero creo que con el póster original y tratando de racionalizar que esto es algo bueno me parece extraño, especialmente teniendo en cuenta que las mismas limitaciones no se aplican cuando se hereda directamente de un interfaz.

Además, la mención sobre el uso de new en lugar de override no se aplica aquí, simplemente no funciona e incluso si lo hiciera no le daría el resultado deseado, es decir, un getter virtual como lo describe la interfaz.

Es posible

tl; dr : puede anular un método de solo obtener un setter si lo desea. Básicamente es solo:

  1. Crea una new propiedad que tenga un get y un set con el mismo nombre.

  2. Si no hace nada más, se llamará al método get anterior cuando se llame a la clase derivada a través de su tipo base. Para solucionar esto, agregue una capa intermedia abstract que utiliza la override en el método get para obligarlo a devolver el resultado del nuevo método get .

Esto nos permite anular las propiedades con get / set incluso si carecían de una en su definición base.

Como beneficio adicional, también puede cambiar el tipo de devolución si lo desea.

  • Si la definición base se get , entonces puede usar un tipo de devolución más derivado.

  • Si la definición base se set -únicamente, entonces puede obtener un tipo de devolución menos derivado.

  • Si la definición base ya fue get / set , entonces:

    • puede usar un tipo de devolución más derivado si lo set ;

    • puede usar un tipo de devolución menos derivado si lo hace get .

En todos los casos, puede conservar el mismo tipo de devolución si lo desea. Los ejemplos a continuación usan el mismo tipo de devolución para simplificar.

Situación: propiedad pre-existente de get -solo

Usted tiene una estructura de clase que no puede modificar. Tal vez es solo una clase, o es un árbol de herencia preexistente. En cualquier caso, desea agregar un método de set a una propiedad, pero no puede.

 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: No puede override el get only con get / set

Desea override con una propiedad get / set , pero no se comstackrá.

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

Solución: use una capa intermedia abstract

Si bien no puede override directamente con una propiedad get / set , puede :

  1. Crea una new propiedad get / set con el mismo nombre.

  2. override el antiguo método get con un acceso al nuevo método get para garantizar la coherencia.

Entonces, primero escribes la capa intermedia 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; } } 

Luego, escribes la clase que no comstackría antes. Comstackrá esta vez porque en realidad no está override la propiedad get only; en su lugar, lo está reemplazando con la new palabra clave.

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

Resultado: ¡todo funciona!

Obviamente, esto funciona según lo previsto para 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. 

Todo sigue funcionando según lo previsto al visualizar D como una de sus clases base, por ejemplo, A o B Pero, la razón por la que funciona podría ser un poco menos obvia.

 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. 

Sin embargo, la definición de get de la clase base sigue siendo anulada por la definición de get de la clase derivada, por lo que sigue siendo completamente consistente.

 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. 

Discusión

Este método le permite agregar métodos set para get propiedades. También puedes usarlo para hacer cosas como estas:

  1. Cambie cualquier propiedad a una propiedad get -only, set -only, u get -and- set , independientemente de lo que sea en una clase base.

  2. Cambie el tipo de devolución de un método en clases derivadas.

Los principales inconvenientes son que hay más encoding que hacer y una abstract class extra abstract class en el árbol de herencia. Esto puede ser un poco molesto con los constructores que toman parámetros porque tienen que copiarse / pegarse en la capa intermedia.

Estoy de acuerdo en que no poder anular un getter en un tipo derivado es un antipatrón. Solo lectura especifica la falta de implementación, no un contrato de funcionalidad pura (implícito en la respuesta máxima del voto).

Sospecho que Microsoft tenía esta limitación ya sea porque se promovió el mismo concepto erróneo, o quizás porque se simplificó la gramática; sin embargo, ahora que se puede aplicar el scope para obtenerlo o establecerlo individualmente, quizás podamos esperar que la anulación también lo sea.

El concepto erróneo indicado por la respuesta del voto superior, que una propiedad de solo lectura de alguna manera debería ser más “pura” que una propiedad de lectura / escritura es ridículo. Simplemente mira muchas propiedades comunes de solo lectura en el marco; el valor no es constante / puramente funcional; por ejemplo, DateTime.Now es de solo lectura, pero todo menos un valor funcional puro. Un bash de “almacenar en caché” un valor de una propiedad de solo lectura suponiendo que devolverá el mismo valor la próxima vez es arriesgado.

En cualquier caso, he utilizado una de las siguientes estrategias para superar esta limitación; ambos son menos que perfectos, pero te permitirán cojear más allá de esta deficiencia en el lenguaje:

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

Quizás puedas resolver el problema creando una nueva propiedad:

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

Puedo entender todos sus puntos, pero efectivamente, las propiedades automáticas de C # 3.0 se vuelven inútiles en ese caso.

No puedes hacer algo como eso:

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

IMO, C # no debe restringir tales escenarios. Es responsabilidad del desarrollador usarlo en consecuencia.

El problema es que, por la razón que sea, Microsoft decidió que debería haber tres tipos distintos de propiedades: de solo lectura, de solo escritura y de lectura y escritura, de las cuales solo una puede existir con una firma determinada en un contexto dado; las propiedades solo pueden ser anuladas por propiedades declaradas de forma idéntica. Para hacer lo que quiera, sería necesario crear dos propiedades con el mismo nombre y firma, una de las cuales era de solo lectura y otra era de lectura y escritura.

Personalmente, me gustaría que todo el concepto de “propiedades” pudiera ser abolido, excepto que la syntax de la propiedad-ish podría usarse como azúcar sintáctico para llamar a los métodos de “obtener” y “establecer”. Esto no solo facilitaría la opción ‘agregar conjunto’, sino que también permitiría que ‘get’ devuelva un tipo diferente de ‘conjunto’. Si bien dicha habilidad no se usaría con demasiada frecuencia, a veces podría ser útil tener un método ‘get’ para devolver un objeto contenedor mientras que el ‘conjunto’ podría aceptar un contenedor o datos reales.

Aquí hay una solución para lograr esto usando 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); } 

De esta forma, podemos establecer el valor del objeto en una propiedad que es de solo lectura. ¡Puede que no funcione en todos los escenarios!

Debes modificar el título de tu pregunta para detallar que tu pregunta es únicamente sobre anular una propiedad abstracta, o que tu pregunta es sobre anular generalmente la propiedad de obtener una sola clase.


Si el primero (anulando una propiedad abstracta)

Ese código es inútil. Una clase base por sí sola no debería decirle que está obligado a anular una propiedad Obtener solo (quizás una interfaz). Una clase base proporciona una funcionalidad común que puede requerir una aportación específica de una clase implementadora. Por lo tanto, la funcionalidad común puede hacer llamadas a propiedades o métodos abstractos. En el caso dado, los métodos de funcionalidad comunes deberían pedirle que anule un método abstracto como:

 public int GetBar(){} 

Pero si no tiene control sobre eso, y la funcionalidad de la clase base lee de su propia propiedad pública (raro), simplemente haga esto:

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

Quiero señalar el comentario (extraño): diría que una mejor práctica es que una clase no use sus propias propiedades públicas, sino que use sus campos privados / protegidos cuando existan. Entonces este es un mejor patrón:

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

Si esto último (anulando la propiedad de obtener una sola clase)

Cada propiedad no abstracta tiene un colocador. De lo contrario, es inútil y no debería importar usarlo. Microsoft no tiene que permitirte hacer lo que quieras. La razón de ser: el colocador existe de una forma u otra, y usted puede lograr lo que quiere Veerryy fácilmente.

La clase base, o cualquier clase en la que pueda leer una propiedad con {get;} , tiene ALGUN tipo de setter expuesto para esa propiedad. Los metadatos se verán así:

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

Pero la implementación tendrá dos extremos del espectro de la complejidad:

Menos complejo:

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

Más complejo:

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

Mi punto es, de cualquier forma, tienes al colocador expuesto. En su caso, es posible que desee sobrescribir int Bar porque no desea que la clase base lo maneje, no tiene acceso para revisar cómo lo está manejando, o se le ha encomendado la tarea de utilizar algún código real rápidamente contra las estadísticas. tu voluntad.

Tanto en Último como en Antiguo (Conclusión)

Long Story: no es necesario que Microsoft cambie nada. Puede elegir cómo se configura su clase de implementación y, sin el constructor, usar toda o ninguna de la clase base.

Solución para solo un pequeño subconjunto de casos de uso, pero sin embargo: en C # 6.0 el “set-in” de “solo lectura” se agrega automáticamente para las propiedades de getter solo anuladas.

 public abstract class BaseClass { public abstract int Bar { get; } } public class ConcreteClass : BaseClass { public override int Bar { get; } public ConcreteClass(int bar) { Bar = bar; } } 

Porque en el nivel IL, una propiedad de lectura / escritura se traduce en dos métodos (getter y setter).

Al anular, debe seguir respaldando la interfaz subyacente. Si pudieras agregar un colocador, estarías efectivamente agregando un nuevo método, que permanecería invisible para el mundo exterior, en lo que respecta a la interfaz de las clases.

Es cierto que agregar un nuevo método no estaría rompiendo la compatibilidad per se, pero dado que permanecería oculto, la decisión de no permitir esto tiene mucho sentido.

Porque eso rompería el concepto de encapsulación y ocultamiento de la implementación. Considere el caso cuando crea una clase, la envía y luego el consumidor de su clase puede establecer una propiedad para la que originalmente solo proporciona un getter. Interrumpiría efectivamente cualquier invariante de su clase de la que pueda depender en su implementación.

Porque una clase que tiene una propiedad de solo lectura (sin setter) probablemente tenga una buena razón para ello. Puede que no haya ningún almacén de datos subyacente, por ejemplo. Permitirte crear un setter rompe el contrato establecido por la clase. Es solo malo POO.

Una propiedad de solo lectura en la clase base indica que esta propiedad representa un valor que siempre se puede determinar desde dentro de la clase (por ejemplo, un valor enum que coincida con el contexto (db-) de un objeto). Entonces la responsabilidad de determinar el valor permanece dentro de la clase.

Agregar un setter ocasionaría un problema incómodo aquí: un error de validación debería ocurrir si establece el valor a cualquier otra cosa que no sea el único valor posible que ya tiene.

Las reglas a menudo tienen excepciones, sin embargo. Es muy posible que, por ejemplo, en una clase derivada, el contexto reduzca los posibles valores enum a 3 de cada 10, pero el usuario de este objeto aún necesita decidir cuál es el correcto. La clase derivada necesita delegar la responsabilidad de determinar el valor para el usuario de este objeto. Es importante darse cuenta de que el usuario de este objeto debe conocer esta excepción y asumir la responsabilidad de establecer el valor correcto.

Mi solución en este tipo de situaciones sería dejar la propiedad como de solo lectura y agregar una nueva propiedad de lectura y escritura a la clase derivada para admitir la excepción. La anulación de la propiedad original simplemente devolverá el valor de la nueva propiedad. La nueva propiedad puede tener un nombre propio que indique el contexto de esta excepción correctamente.

Esto también es compatible con la observación válida: “hacer que sea lo más difícil posible para los malentendidos que surjan” por Gishu.

Esto no es imposible Simplemente tiene que usar la palabra clave “nueva” en su propiedad. Por ejemplo,

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

Parece como si este enfoque satisface a ambos lados de esta discusión. El uso de “nuevo” rompe el contrato entre la implementación de la clase base y la implementación de la subclase. Esto es necesario cuando una Clase puede tener múltiples contratos (ya sea a través de la interfaz o la clase base).

Espero que esto ayude