¿Por qué no se permite el siguiente código C #?
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 se puede anular porque 'BaseClass.Bar' no tiene un descriptor de acceso reemplazable
c#
.net
properties
getter-setter
destripador234
fuente
fuente
Foo
que no hace más que envolver unGetFoo
método abstracto protegido . Un tipo derivado abstracto puede sombrear la propiedad con una propiedad de lectura-escritura no virtual que no hace nada más que envolver elGetFoo
método antes mencionado junto con unSetFoo
método abstracto ; un tipo derivado concreto podría hacer lo anterior pero proporcionar definiciones paraGetFoo
ySetFoo
.Respuestas:
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 programador al que se le ha dicho que codifique contra la derivación de Baseclass. Escribo algo que supone que no se puede escribir en Bar (ya que la Clase Base declara explícitamente que es una propiedad get only). Ahora con su derivación, mi código puede romperse. p.ej
Como Bar no se puede configurar según la interfaz BaseClass, BarProvider supone que el almacenamiento en caché es algo seguro, ya que Bar no se puede modificar. Pero si el conjunto fuera posible en una derivación, esta clase podría estar sirviendo 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 vuelve más turbio cuando lo pienso.
Una interfaz es un contrato que dice 'espera 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 get-only en una interfaz, la leo como 'Cualquier implementación expondría este atributo Bar' ... en una clase base hace clic como '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 ... Terminaría diciendo 'haz que sea lo más difícil posible que surjan malentendidos'.
fuente
InvalidOperationException("This instance is read-only")
.Creo que la razón principal es simplemente que la sintaxis es demasiado explícita para que esto funcione de otra manera. Este código:
es bastante explícito que tanto the
get
como theset
son anulaciones. No hayset
en la clase base, por lo que el compilador se queja. Al igual que no puede anular un método que no está definido en la clase base, tampoco puede anular un setter.Puede decir que el compilador debe adivinar su intención y solo aplicar la anulación al método que se puede anular (es decir, el captador en este caso), pero esto va en contra de uno de los principios de diseño de C #: que el compilador no debe adivinar sus intenciones , porque puede adivinar mal sin que lo sepas.
Creo que la siguiente sintaxis podría funcionar bien, pero como Eric Lippert sigue diciendo, implementar incluso una característica menor como esta sigue siendo una gran cantidad de esfuerzo ...
o, para propiedades autoimplementadas,
fuente
Me encontré con el mismo problema hoy y creo que tengo una razón muy válida para querer esto.
Primero, me gustaría argumentar que tener una propiedad get-only no necesariamente se traduce en solo lectura. Lo interpreto como "De esta interfaz / resumen puedes obtener este valor", eso no significa que alguna implementación de esa interfaz / clase abstracta no necesitará que el usuario / programa 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 setter 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, que se habría complicado innecesariamente si tuviera que agregar otra propiedad solo para el propósito de serialización.
Sé que esto es solo una publicación de "mis 2 centavos", pero siento que con el póster original y tratar de racionalizar que esto es algo bueno me parece extraño, especialmente teniendo en cuenta que las mismas limitaciones no se aplican al heredar directamente de un interfaz.
Además, la mención sobre el uso de nuevo en lugar de anulación no se aplica aquí, simplemente no funciona e incluso si lo hiciera no le daría el resultado deseado, es decir, un captador virtual como se describe en la interfaz.
fuente
Es posible
tl; dr : puede anular un método get-only con un setter si lo desea. Básicamente es solo:
Cree una
new
propiedad que tenga aget
y aset
usando el mismo nombre.Si no hace nada más,
get
se seguirá llamando al método anterior cuando se llame a la clase derivada a través de su tipo base. Para solucionar esto, agregue unaabstract
capa intermedia que useoverride
en elget
método anterior para forzarlo a devolver elget
resultado del nuevo método.Esto nos permite anular 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 era
get
-solo, entonces puede usar un tipo de retorno más derivado.Si la definición base era
set
-solo, entonces puede usar un tipo de retorno menos derivado.Si la definición base ya era
get
/set
, entonces:puede usar un tipo de retorno más derivado si lo hace
set
-solo;puede usar un tipo de retorno menos derivado si lo hace solo
get
.En todos los casos, puede mantener el mismo tipo de retorno si lo desea. Los siguientes ejemplos usan el mismo tipo de retorno por simplicidad.
Situación:
get
propiedad preexistente soloTiene alguna 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
set
método a una propiedad, pero no puede.Problema: No puede
override
elget
-sólo conget
/set
Desea
override
con una propiedadget
/set
, pero no se compilará.Solución: use una
abstract
capa intermediaSi bien no puede directamente
override
con una propiedadget
/set
, puede :Cree una propiedad
new
get
/set
con el mismo nombre.override
Elget
método anterior con un descriptor de acceso al nuevoget
método para garantizar la coherencia.Entonces, primero escribes la
abstract
capa intermedia:Luego, escribe la clase que no se compilaría antes. Se va a compilar esta vez porque no estás en realidad
override
'ing laget
propiedad -sólo; en cambio, lo está reemplazando con lanew
palabra clave.Resultado: ¡Todo funciona!
Obviamente, esto funciona según lo previsto
D
.Todo sigue funcionando según lo previsto cuando se ve
D
como una de sus clases base, por ejemplo,A
oB
. Pero, la razón por la que funciona podría ser un poco menos obvio.Sin embargo, la definición de clase base de
get
todavía está anulada en última instancia por la definición de la clase derivada deget
, por lo que sigue siendo completamente coherente.Discusión
Este método le permite agregar
set
métodos a sologet
propiedades. También puedes usarlo para hacer cosas como:Cambie cualquier propiedad a una propiedad
get
-only,set
-only oget
-and-set
, independientemente de lo que haya sido en una clase base.Cambiar el tipo de retorno de un método en clases derivadas.
Los principales inconvenientes son que hay más codificación que hacer y un extra
abstract class
en el árbol de herencia. Esto puede ser un poco molesto con los constructores que toman parámetros porque deben copiarse / pegarse en la capa intermedia.fuente
Estoy de acuerdo en que no poder anular un captador en un tipo derivado es un antipatrón. Solo lectura especifica la falta de implementación, no un contrato de un funcional puro (implícito en la respuesta de voto superior).
Sospecho que Microsoft tenía esta limitación, ya sea porque se promovió el mismo concepto erróneo, o tal vez por simplificar la gramática; sin embargo, ahora que se puede aplicar el alcance para obtener o establecer individualmente, tal vez podamos esperar que la anulación también se pueda aplicar.
La idea errónea indicada por la respuesta del voto superior, que una propiedad de solo lectura debería ser de alguna manera más "pura" que una propiedad de lectura / escritura es ridícula. Simplemente observe 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 cualquier cosa menos un valor funcional puro. Un intento 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 usado 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 del lenguaje:
fuente
Quizás podría solucionar el problema creando una nueva propiedad:
fuente
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 nada así:
OMI, C # no debe restringir tales escenarios. Es responsabilidad del desarrollador usarlo en consecuencia.
fuente
El problema es que, por cualquier razón, Microsoft decidió que debería haber tres tipos distintos de propiedades: solo lectura, solo escritura y lectura-escritura, de las cuales solo una puede existir con una firma dada en un contexto dado; las propiedades solo pueden ser anuladas por propiedades declaradas idénticamente. Para hacer lo que quiera, sería necesario crear dos propiedades con el mismo nombre y firma: una de solo lectura y otra de lectura y escritura.
Personalmente, deseo que se elimine todo el concepto de "propiedades", excepto que la sintaxis de propiedad-ish podría usarse como azúcar sintáctica para llamar a los métodos "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 capacidad no se usaría con demasiada frecuencia, a veces podría ser útil que un método 'get' devuelva un objeto contenedor mientras que el 'conjunto' podría aceptar un contenedor o datos reales.
fuente
Aquí hay una solución alternativa para lograr esto usando Reflection:
De esta manera, podemos establecer el valor del objeto en una propiedad que es de solo lectura. ¡Aunque podría no funcionar en todos los escenarios!
fuente
Debe modificar el título de su pregunta para detallar que su pregunta se refiere únicamente a la anulación de una propiedad abstracta, o que su pregunta se refiere a la anulación general de la propiedad get-only de una clase.
Si el primero (anula 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 Get-Only (Quizás una interfaz). Una clase base proporciona una funcionalidad común que puede requerir informació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:
Pero si no tiene control sobre eso, y la funcionalidad de la clase base se lee desde su propia propiedad pública (extraño), simplemente haga esto:
Quiero señalar el comentario (extraño): diría que una práctica recomendada es que una clase no use sus propias propiedades públicas, sino que use sus campos privados / protegidos cuando existan. Entonces este es un patrón mejor:
Si este último (anula la propiedad get-only de una clase)
Toda propiedad no abstracta tiene un setter. De lo contrario, es inútil y no debería importarle usarlo. Microsoft no tiene que permitirte hacer lo que quieras. La razón es: el setter existe de una forma u otra, y puede lograr lo que quiere Veerryy fácilmente.
La clase base, o cualquier clase en la que pueda leer una propiedad
{get;}
, tiene ALGUNOS tipos de establecedores expuestos para esa propiedad. Los metadatos se verán así:Pero la implementación tendrá dos extremos del espectro de complejidad:
Menos complejo:
Más complejo:
Mi punto es que, de cualquier manera, tienes al setter expuesto. En su caso, es posible que desee anular
int Bar
porque no desea que la clase base lo maneje, no tenga acceso para revisar cómo lo maneja, o se le encargó que manipule algún código realmente rápido y sucio contra su voluntad .Tanto en el último como en el anterior (conclusión)
Long Story Short: 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 clase base.
fuente
Solución para solo un pequeño subconjunto de casos de uso, pero de todos modos: en C # 6.0 se establece automáticamente el setter "readonly" para las propiedades anuladas de getter only.
fuente
Porque eso rompería el concepto de encapsulación y ocultamiento de la implementación. Considere el caso cuando cree una clase, envíela, y luego el consumidor de su clase podrá establecer una propiedad para la que originalmente proporcionó solo un captador. Efectivamente interrumpiría cualquier invariante de su clase de la que pueda depender en su implementación.
fuente
Esto no es imposible. Simplemente tiene que usar la palabra clave "nueva" en su propiedad. Por ejemplo,
Parece que este enfoque satisface 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
fuente
new
ya no anula la base virtual. En particular, si la propiedad base es abstracta , como lo es en la publicación original, es imposible usar lanew
palabra clave en una subclase no abstracta porque tiene que anular todos los miembros abstractos.Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)
le da 5,Console.WriteLine(b.BaseProperty)
le da 0, y cuando su sucesor tiene que depurar esto, es mejor que se haya mudado muy, muy lejos.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 de enumeración que coincida con el contexto (db-) de un objeto). Entonces, la responsabilidad de determinar el valor permanece dentro de la clase.
Agregar un setter podría causar un problema incómodo aquí: un error de validación debería ocurrir si establece el valor en otra cosa que no sea el único valor posible que ya tiene.
Sin embargo, las reglas a menudo tienen excepciones. Es muy posible que, por ejemplo, en una clase derivada, el contexto reduzca los posibles valores de enumeración a 3 de 10, pero el usuario de este objeto aún debe decidir cuál es el correcto. La clase derivada necesita delegar la responsabilidad de determinar el valor al usuario de este objeto. Es importante darse cuenta de que el usuario de este objeto debe ser consciente de 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 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 respalda la observación válida: "haga que sea lo más difícil posible que surjan malentendidos" de Gishu.
fuente
ReadableMatrix
clase (con una propiedad indexada de solo lectura de dos argumentos) que tenga subtiposImmutableMatrix
y no implicaría incomodidad algunaMutableMatrix
. El código que solo necesita leer una matriz, y no le importa si puede cambiar en algún momento en el futuro, podría aceptar un parámetro de tipoReadableMatrix
y estar perfectamente satisfecho con los subtipos mutables o inmutables.List<T>.Count
hace: no se puede asignar, pero eso no significa que sea de "solo lectura", ya que no puede ser modificado por los usuarios de la clase. Se puede modificar muy bien, aunque sea indirectamente, agregando y eliminando elementos de la lista.Porque en el nivel IL, una propiedad de lectura / escritura se traduce en dos métodos (getter y setter).
Al anular, debe seguir admitiendo la interfaz subyacente. Si pudiera agregar un setter, efectivamente estaría agregando un nuevo método, que permanecería invisible para el mundo exterior, en lo que respecta a la interfaz de sus 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.
fuente
new
lugar deoverride
.new
con sus adiciones.Debido a que una clase que tiene una propiedad de solo lectura (sin setter) probablemente tiene una buena razón para ello. Puede que no haya ningún almacén de datos subyacente, por ejemplo. Permitirle crear un setter rompe el contrato establecido por la clase. Es simplemente malo OOP.
fuente