¿Por qué es imposible anular una propiedad de solo getter y agregar un setter? [cerrado]

141

¿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

destripador234
fuente
9
StackOverflow tiene una capacidad limitada para responder preguntas en nombre de Microsoft. Considere reformular su pregunta.
Jay Bazuzi
1
Por cierto, un remedio simple para este comportamiento molesto en .net es tener una propiedad de solo lectura no virtual Fooque no hace más que envolver un GetFoomé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 el GetFoométodo antes mencionado junto con un SetFoométodo abstracto ; un tipo derivado concreto podría hacer lo anterior pero proporcionar definiciones para GetFooy SetFoo.
supercat
1
Solo para agregar combustible al fuego, C ++ / CLI lo permite.
ymett
1
La posibilidad de agregar un descriptor de acceso al anular una propiedad se ha propuesto como un cambio para una versión futura de C # ( github.com/dotnet/roslyn/issues/9482 ).
Brandon Bonds
1
Para ser sincero, esta pregunta a menudo se plantea cuando es necesario extender un tipo de biblioteca existente que usa el siguiente patrón: clase base de solo lectura, clase mutable derivada. Debido a que la biblioteca ya usa un patrón incorrecto (como WPF), surge la necesidad de solucionar este patrón incorrecto. La conferencia no le ahorra a uno tener que implementar una solución alternativa al final del día.
rwong 01 de

Respuestas:

-4

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

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 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'.

Gishu
fuente
94
Sigo sin estar convencido. Incluso si no hay un configurador explícito, esto no garantiza que la propiedad siempre devolverá el mismo objeto; todavía puede cambiarse por algún otro método.
ripper234
34
¿No debería ser lo mismo para las interfaces, entonces? Puede tener propiedad de solo lectura en la interfaz y lectura / escritura en el tipo de implementación.
Ilya Ryzhenkov
49
No estoy de acuerdo: la clase base solo declaró que la propiedad no tenía setter; eso no es lo mismo que la propiedad es de solo lectura. "Solo lectura" es solo un significado que usted le asigna. No hay garantías relevantes integradas en C # en absoluto. Podría tener una propiedad get-only que cambia cada vez, o una propiedad get / set donde el setter arroja un InvalidOperationException("This instance is read-only").
Roman Starkov
21
Mirando a los captadores y establecedores como métodos, no veo por qué debería ser un "incumplimiento de contrato" más que agregar un nuevo método. Un contrato de clase base no tiene nada que ver con qué funcionalidad no está presente, solo con lo que está presente.
Dave Cousineau
37
-1 No estoy de acuerdo con esta respuesta, y la encuentro equivocada. Dado que una clase base define el comportamiento que cualquier clase debe cumplir (consulte el Principio de sustitución de Liskov), pero no (y no debe) restringir para agregar comportamiento. Una clase base solo puede definir cuál debe ser el comportamiento, y no puede (y no debe) especificar cuál es el comportamiento 'no debe ser' (es decir, 'no debe poder escribirse en niveles concretos)'.
Marcel Valdez Orozco
39

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:

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

es bastante explícito que tanto the getcomo the setson anulaciones. No hay seten 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 ...

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

o, para propiedades autoimplementadas,

public int MyProperty { override get; set; }
Roman Starkov
fuente
19

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.

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

JJJ
fuente
2
O, en mi caso, {get; conjunto privado; }. No estoy violando ningún contrato ya que el conjunto sigue siendo privado y solo se refiere a la clase derivada.
Machtyn
16

Es posible

tl; dr : puede anular un método get-only con un setter si lo desea. Básicamente es solo:

  1. Cree una newpropiedad que tenga a gety a setusando el mismo nombre.

  2. Si no hace nada más, getse seguirá llamando al método anterior cuando se llame a la clase derivada a través de su tipo base. Para solucionar esto, agregue una abstractcapa intermedia que use overrideen el getmétodo anterior para forzarlo a devolver el getresultado del nuevo método.

Esto nos permite anular propiedades con get/ setincluso 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: getpropiedad preexistente solo

Tiene 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 setmétodo 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 overrideel get-sólo con get/set

Desea overridecon una propiedad get/ set, pero no se compilará.

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

Solución: use una abstractcapa intermedia

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

  1. Cree una propiedad new get/ setcon el mismo nombre.

  2. overrideEl getmétodo anterior con un descriptor de acceso al nuevo getmétodo para garantizar la coherencia.

Entonces, primero escribes la abstractcapa intermedia:

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, escribe la clase que no se compilaría antes. Se va a compilar esta vez porque no estás en realidad override'ing la getpropiedad -sólo; en cambio, lo está reemplazando con la newpalabra 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 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 cuando se ve Dcomo una de sus clases base, por ejemplo, Ao B. Pero, la razón por la que funciona podría ser un poco menos obvio.

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 clase base de gettodavía está anulada en última instancia por la definición de la clase derivada de get, por lo que sigue siendo completamente coherente.

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 setmétodos a solo getpropiedades. También puedes usarlo para hacer cosas como:

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

  2. 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 classen 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.

Nat
fuente
Le di a esto su propia pregunta: stackoverflow.com/questions/22210971
Nat
Buen enfoque (+1). Sin embargo, dudo que esto funcione sin problemas con el marco de la entidad.
Mike de Klerk
9

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:

   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;
   }
T.Tobler
fuente
DateTime.Now es puramente funcional, ya que no tiene efectos secundarios (el hecho de que tarde en ejecutarse podría llamarse un efecto secundario, pero dado que el tiempo de ejecución, al menos cuando está limitado y es corto) no se considera un efecto secundario de cualquier otro método, tampoco lo consideraría aquí).
supercat
1
Hola supercat Tiene razón sobre los efectos secundarios observables externamente que impiden que una función sea pura, pero la otra restricción es que el valor de resultado de una función pura solo debe depender de los parámetros. Por lo tanto, una propiedad estática pura, como función, debe ser inmutable y constante. Un compilador sofisticado puede reemplazar todas las llamadas posteriores a una función pura sin parámetros con el resultado devuelto por la primera llamada.
T.Tobler
Tienes razón en que mi terminología era imprecisa, pero creo que lo apropiado de que algo sea una propiedad de solo lectura en lugar de un método depende de la falta de efectos secundarios, en lugar de la pureza.
supercat
2

Quizás podría solucionar el problema creando una nueva propiedad:

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

int IBase.Bar { 
  get { return Bar; }
}
Hallgrim
fuente
44
Pero solo puede hacer esto cuando implementa una interfaz, no cuando hereda de una clase base. Tendrías que elegir otro nombre.
svick
Supongo que la clase de ejemplo implementa IBase (algo así como la interfaz pública IBase {int Bar {get;}}), de lo contrario, en la implementación de la clase, int IBase.Bar {...} no está permitido. Luego, simplemente puede crear una barra de propiedades regular con get y set, aunque la propiedad Bar de la interfaz no requiere un conjunto.
Ibrahim ben Salah
1

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í:

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

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

Thomas Danecker
fuente
No entiendo tu punto. ¿Estás de acuerdo con que C # debería permitir agregar un setter, o estás con la mayoría de las otras personas que respondieron esta pregunta?
ripper234
2
Como dije, en mi opinión, C # debería permitir agregar un setter. Es responsabilidad del desarrollador usarlo en consecuencia.
Thomas Danecker el
1

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.

Super gato
fuente
0

Aquí hay una solución alternativa 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 manera, podemos establecer el valor del objeto en una propiedad que es de solo lectura. ¡Aunque podría no funcionar en todos los escenarios!

usuario2514880
fuente
1
¿Está seguro de que puede llamar a un setter mediante reflexión que no existe (como sucede en las propiedades de getter descritas)?
O Mapper
0

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:

public int GetBar(){}

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:

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

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 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í:

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

Pero la implementación tendrá dos extremos del espectro de 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 que, de cualquier manera, tienes al setter expuesto. En su caso, es posible que desee anular int Barporque 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.

Suamere
fuente
0

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.

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

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

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}
lxa
fuente
-1

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.

petr k.
fuente
1
-1: Tener un setter no permite automáticamente que un descendiente rompa cualquier invariante. El setter solo puede cambiar cosas que el descendiente ya puede cambiar de todos modos.
Roman Starkov
@RomanStarkov Eso es cierto. Pero la pregunta de esta publicación es; ¿Por qué no puede no agregar un setter a una abstracción que no lo contrate? Su comentario sería apropiado en la pregunta opuesta; ¿Por qué Microsoft le permite crear una propiedad ReadOnly en una abstracción si el descendiente tiene el control total de sus campos privados?
Suamere
+1: Esta pregunta es acerca de por qué Microsoft no le permite romper un contrato. Aunque la razón de sus votos negativos es más acerca de por qué Microsoft permite que un contrato incluya solo propiedades de solo lectura. Entonces esta respuesta es correcta en el contexto.
Suamere
@Suamere, el contrato no establece que la propiedad no se pueda cambiar. Todo lo que dice es que existe una propiedad que se puede leer. Agregar un setter no romperá este contrato. Puede interpretarlo como una intención de crear una propiedad de solo lectura, pero no puede garantizar en C # 6 que no haya forma de cambiarla, por lo tanto, el contrato que está pensando no está realmente allí. Piense en una interfaz: puede ofrecer un contrato en una propiedad gettable (¡no get-only!). Esta interfaz se puede implementar mediante una propiedad get + set muy bien.
Roman Starkov
Estás abordando esto desde la perspectiva del autor concreto. Puede cambiarlo como quiera. Lo estoy abordando desde la perspectiva del autor de la clase base -> Aproximación como consumidor final de la clase implementadora. El autor de la clase base no quiere que ningún consumidor de las futuras clases concretas pueda cambiar esa propiedad sin que el autor de la clase concreta exponga un método especial para manejar caso por caso. Porque en la clase base, esa propiedad se trata como inmutable, y eso se comunica al exponerla como tal.
Suamere
-1

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

mbolt35
fuente
29
Esta respuesta es incorrecta porque la propiedad marcada newya no anula la base virtual. En particular, si la propiedad base es abstracta , como lo es en la publicación original, es imposible usar la newpalabra clave en una subclase no abstracta porque tiene que anular todos los miembros abstractos.
Timwi
Esto es fenomenalmente peligroso porque lo siguiente arroja resultados diferentes a pesar de ser el mismo objeto: 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.
Mark Sowul
Estoy de acuerdo con Mark, esto es extremadamente desagradable. Si ambas implementaciones de BaseProperty estuvieran respaldadas por la misma variable, estaría bien aunque en mi humilde opinión. IE: Proteja _baseProperty y elimine _testBaseProperty. Además, AFAIK, la implementación en Base no necesita ser virtual ya que en realidad no la está anulando.
Steazy
La mayoría de las respuestas a las preguntas pueden tener opiniones y opiniones válidas. Pero aparte de los comentarios anteriores, mi principal crítica sobre esta respuesta es que creo que la pregunta real puede ser literalmente sobre propiedades abstractas en una base de código sobre la cual el consumidor no tiene control. Entonces, mi crítica es que esta respuesta puede no encajar en la pregunta.
Suamere
-1

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.

Remco te Wierik
fuente
1
Una ReadableMatrixclase (con una propiedad indexada de solo lectura de dos argumentos) que tenga subtipos ImmutableMatrixy no implicaría incomodidad alguna MutableMatrix. 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 tipo ReadableMatrixy estar perfectamente satisfecho con los subtipos mutables o inmutables.
supercat
Un setter adicional podría modificar una variable privada dentro de la clase. La llamada propiedad de "solo lectura" de la clase base determinaría el valor "desde dentro de la clase" (leyendo la nueva variable privada). No veo el problema allí. Observe también, por ejemplo, lo que List<T>.Counthace: 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.
O Mapper
-2

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.

Ismaeel
fuente
2
No me importa mucho "el mundo exterior" aquí. Quiero agregar funcionalidad a una clase, que siempre es visible solo para el código que conoce la clase específica y no la base.
ripper234
@ ripper234 Consulte la respuesta de Matt Bolt: si desea que la nueva propiedad sea visible solo para los usuarios de la clase derivada, debe usarla en newlugar de override.
Dan Berindei
1
No permanece oculto. Es como decir que no puede agregar un nuevo método a una clase secundaria porque el nuevo método "permanecerá oculto del mundo exterior". Por supuesto, permanece oculto desde la perspectiva de la clase base, pero es accesible desde los clientes de la clase secundaria. (Es decir, para afectar lo que el comprador debe devolver)
Sheepy
Sheepy, eso no es lo que digo en absoluto. Quiero decir, exactamente como usted señaló, permanecería oculto "desde una perspectiva de clase base". Es por eso que agregué "en lo que respecta a la interfaz de sus clases ". Si está subclasificando una clase base, es casi seguro que el "mundo exterior" se referirá a sus instancias de objeto con el tipo de clase base. Si usarían directamente su clase concreta, no necesitaría compatibilidad con la clase base y podría, por supuesto, usarla newcon sus adiciones.
Ismaeel
-2

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.

Joel Coehoorn
fuente
Up-Vote. Esto es muy sucinto. ¿Por qué Microsoft debería permitir que el consumidor de una clase base rompa el contrato? Estoy de acuerdo con esta versión muy corta de mi respuesta demasiado larga y confusa. Jajaja Si eres un consumidor de una clase base, no deberías querer romper el contrato, pero PUEDES implementarlo de dos maneras diferentes.
Suamere