¿Por qué es imposible para anular un getter-sólo a la propiedad y agregar un setter?[cerrado]

StackOverflow https://stackoverflow.com/questions/82437

Pregunta

¿Por qué el siguiente código en C# no se permite:

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

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

CS0546 'ConcreteClass.De la barra.set":no se puede anular porque 'BaseClass.La barra no tiene un overridable descriptor de acceso set

¿Fue útil?

Solución

Porque el escritor de Baseclass ha declarado explícitamente que la Barra tiene que ser una propiedad de sólo lectura.No tiene sentido para derivaciones a romper este contrato y asegúrese de lectura-escritura.

Estoy con Microsoft en este.
Digamos que soy un nuevo programador que ha sido dicho código en contra de la clase base de la derivación.puedo escribir algo que se supone que la Barra no se puede escribir (desde la clase base se establece expresamente que se trata de un sólo get de la propiedad).Ahora, con su derivación, mi código puede romper.por ejemplo,

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

  public void setSource(BaseClass b)
  {
    _source = b;
    _currentBar = b.Bar;
  }

  public Bar getBar()
  { return _currentBar;  }
}

Desde el Bar no se pueden ajustar según el BaseClass interfaz, BarProvider se supone que el almacenamiento en caché es una cosa segura para hacer - Desde la Barra no puede ser modificado.Pero si se establece que es posible en una derivación, esta clase podría servir obsoletos valores si alguien modifica el _source del objeto de la Barra de propiedades externamente.El punto de ser"Ser Abierto, evitar hacer sneaky cosas y sorprendente para la gente'

Actualización: Ilya Ryzhenkov se pregunta " ¿por Qué no las interfaces de jugar con las mismas reglas, entonces?' Hmm..esto se vuelve más turbio cuando pienso acerca de eso.
Una interfaz es un contrato que dice que 'esperar de una aplicación a tener una lectura de la propiedad nombre de Bar.' Personalmente Yo soy mucho menos propensos a hacer la suposición de sólo lectura si vi a una Interfaz.Cuando veo una propiedad única en una interfaz, lo he leído como "Cualquier aplicación de exponer este atributo Bar'...sobre una base de clase que encaje como 'Bar es una propiedad de sólo lectura'.Por supuesto, técnicamente no estás rompiendo el contrato..usted está haciendo algo más.Así que tienes razón, en un sentido..Me gustaría concluir diciendo que 'hacer que sea tan duro como sea posible para los malentendidos a los cultivos'.

Otros consejos

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

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

es bastante explícito el hecho de que tanto la get y el set se anula.No hay set en la clase base, por lo que el compilador se queja.Igual que no se puede invalidar un método que no está definido en la clase base, no se puede invalidar un organismo cualquiera.

Se podría decir que el compilador debe adivinar su intención y sólo aplicar la anulación del método que se puede eliminar (es decir,el captador en este caso), pero esto va en contra de uno de los C# principios de diseño - que el compilador no debe adivinar sus intenciones, porque puede adivinar mal sin que usted lo sepa.

Creo que la siguiente sintaxis pueden hacerlo muy bien, pero como Eric Lippert, sigue diciendo, la aplicación incluso un menor de edad esta característica es todavía una gran cantidad de esfuerzo...

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

o, para autoimplemented propiedades,

public int MyProperty { override get; set; }

Me encontré con el mismo problema de hoy y creo que tener una razón muy válida para querer esto.

En primer lugar me gustaría decir que disponer de una única propiedad no necesariamente se traduce en sólo lectura.Yo lo interpreto como "a partir De esta interfaz/abtract usted puede obtener este valor", que no significa que algunos de implementación de la interfaz/clase abstracta, no es necesario que el usuario/programa para establecer este valor de forma explícita.Las clases abstractas servir a los fines de la aplicación de una parte de la funcionalidad necesaria.No veo absolutamente ninguna razón por la que una clase heredada no podía agregar un setter sin violar los contratos.

El siguiente es un ejemplo simplificado de lo que se necesita hoy en día.Terminé teniendo que añadir un setter en mi interfaz sólo para conseguir alrededor de esto.La razón para agregar el setter y no añadir, por ejemplo, un SetProp método es que una implementación particular de la interfaz que se utiliza DataContract/DataMember para la serialización de la Proposición, que se habría realizado innecesariamente complicado si he tenido que añadir otra propiedad sólo para el propósito de la 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 sólo una "mis 2 centavos" de post, pero me siento con el cartel original y tratando de racionalizar que esta es una buena cosa que me parece extraño, sobre todo teniendo en cuenta que las mismas limitaciones no se aplican cuando heredar directamente desde una interfaz.

También la mención sobre el uso de nuevo en lugar de reemplazar no se aplica aquí, simplemente no funciona y aún si lo hiciera no dará el resultado que quería, es decir, un virtual getter como se describe por la interfaz.

Es posible

tl;dr- Puede reemplazar un único método con un setter, si quieres.Es básicamente:

  1. Crear un new propiedad que tiene una get y un set utilizando el mismo nombre.

  2. Si no hacemos nada más, entonces el viejo get método todavía será llamado cuando la clase derivada se llama a través de su tipo base.Para solucionar este problema, agregue un abstract capa intermedia que se utiliza override en el antiguo get método para obligar a devolver el nuevo get el método es el resultado.

Esto nos permite reemplazar las propiedades con get/set incluso si se carecía de uno en su definición de la base.

Como un bono, usted puede también cambiar el tipo de retorno, si quieres.

  • Si la definición de la base fue get-sólo, entonces usted puede utilizar un derivado de tipo de retorno.

  • Si la definición de la base fue set-sólo, entonces usted nos puede menos de derivados de tipo de retorno.

  • Si la definición de la base de que ya estaba get/set, y , a continuación:

    • usted puede utilizar más de derivados de tipo de retorno si usted puede hacer set-sólo;

    • usted puede utilizar un menor de derivados de tipo de retorno si usted puede hacer get-sólo.

En todos los casos, puede mantener el mismo tipo de retorno, si quieres.Los ejemplos a continuación, utilice el mismo tipo de retorno de la simplicidad.

Situación:Pre-existente get-sólo la propiedad

Usted tiene cierta clase de estructura que no se puede modificar.Tal vez es sólo una clase, o es un pre-existentes árbol de herencia.Cualquiera que sea el caso, desea agregar un set método para 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 se puede override el get-sólo con get/set

Desea override con un get/set la propiedad, pero no va a compilar.

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

Solución:El uso de un abstract capa intermedia

Mientras que usted no puede directamente override con un get/set la propiedad, que puede:

  1. Crear un new get/set propiedad con el mismo nombre.

  2. override el viejo get método con un descriptor de acceso a la nueva get método para asegurar la consistencia.

Así, primero se escribe el abstract capa 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; }
}

A continuación, escriba la clase que no compile anterior.Va a compilar este tiempo, porque en realidad, no se override'ción de la get-sólo la propiedad;en su lugar, usted está reemplazando el uso de la new la 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 bien!

Obviamente, esto funciona como la intención de 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 lo que todavía funciona-como-al ver la intención de D como una de sus clases base, por ejemplo, A o B.Pero, la razón por la que las obras podrían 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 la clase base de get todavía es finalmente reemplazado por el de la clase derivada de la definición de get, de modo que todavía es 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 añadir set métodos para get-sólo las propiedades.También se puede utilizar para hacer cosas como:

  1. Cambiar cualquier propiedad en un get-sólo, set-sólo, o get-y-set la propiedad, independientemente de lo que fue en una clase base.

  2. Cambiar el tipo de retorno de un método en las clases derivadas.

Los principales inconvenientes son que hay más de codificación para hacer y un extra abstract class en el árbol de herencia.Esto puede ser un poco molesto con los constructores que toman los parámetros debido a que deben copiar y pegar en la capa intermedia.

Estoy de acuerdo en que no ser capaz de anular un captador en un tipo derivado es un anti-patrón.Leer-Sólo especifica la falta de la aplicación, no a un contrato de un puro funcional (implícita por el voto superior de respuesta).

Tengo la sospecha de que Microsoft esta limitación, ya sea porque el mismo error fue promovido, o tal vez debido a la simplificación de la gramática;aunque, ahora que el alcance puede ser aplicado para obtener o establecer de forma individual, tal vez podemos tener la esperanza de anular puede ser demasiado.

La idea errónea de lo indicado por el voto superior de respuesta, que una propiedad de sólo lectura debe ser de alguna manera más "pura" de una propiedad de lectura/escritura es ridículo.Sólo tienes que buscar en muchas de las propiedades de solo lectura en el marco;el valor no es una constante / puramente funcional;por ejemplo, DateTime.Ahora es de sólo lectura, pero nada sino un puro valor funcional.Un intento de 'caché' de un valor de una propiedad de sólo lectura suponiendo que devolverá el mismo valor la próxima vez es arriesgado.

En cualquier caso, yo he usado una de las siguientes estrategias para superar esta limitación;ambos son menos que perfecto, pero que permite a cojear más allá de esta deficiencia en el idioma:

   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 podría ir todo el problema mediante la creación de una nueva propiedad:

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

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

Puedo entender que todos sus puntos, pero efectivamente, C# 3.0 automática de propiedades de obtener inútil en ese caso.

Usted no puede hacer algo como esto:

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

De la OMI, C# no debe restringir este tipo de escenarios.Es responsabilidad del desarrollador para su uso adecuado.

El problema es que por alguna razón Microsoft ha decidido que no debe ser de tres tipos distintos de propiedades:de sólo lectura, escribir, y leer-escribir, sólo uno de los cuales puede existir con una determinada firma en un contexto dado;propiedades sólo puede ser reemplazado por una forma idéntica-declaró propiedades.Para hacer lo que quieres sería necesario crear dos propiedades con el mismo nombre y la firma-una de las cuales fue de sólo lectura, y uno de los cuales era el de lectura-escritura.

Personalmente, me gustaría que todo el concepto de "propiedades" podría ser abolida, salvo que la propiedad-ish sintaxis podría ser utilizado como azúcar sintáctico para la llamada "get" y "set" de los métodos.Esto no sólo facilitaría la 'agregar conjunto de la opción, pero también permitiría que 'get' para devolver un tipo diferente de "set".Mientras que dicha capacidad no se utiliza terriblemente a menudo, que a veces puede ser útil disponer de un método 'get' devolver un objeto contenedor, mientras que el 'set' podría aceptar un contenedor o datos reales.

Aquí es un trabajo de todo el fin de lograr esto mediante la Reflexión:

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 se puede establecer el valor del objeto en una propiedad que es de sólo lectura.Puede que no funcione en todos los escenarios, aunque!

Usted debe modificar su pregunta en el título de cualquiera de los detalles que tu pregunta es, únicamente en lo que se refiere a la sustitución de una propiedad abstracta, o que tu pregunta es en lo que respecta generalmente primordial de la clase get-sólo la propiedad.


Si el ex (más allá de una propiedad abstracta)

Ese código es inútil.Una clase base, solo no hay que decirles que usted está obligado a reemplazar una Única propiedad (tal vez una Interfaz).Una clase base proporciona funcionalidad común que puede requerir de entrada específicos de una clase de implementación.Por lo tanto, la funcionalidad común puede hacer llamadas de abstraer las propiedades o métodos.En el caso dado, la funcionalidad común que los métodos deben ser preguntando por usted para reemplazar un método abstracto, tales como:

public int GetBar(){}

Pero si usted no tiene control sobre eso, y la funcionalidad de la clase base se lee de su propio público de la propiedad (raro), a continuación, sólo hacer 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 la (extraña) comentario:Yo diría que una de las mejores prácticas es para una clase a no utilizar sus propias propiedades públicas, pero para uso privado/protegido de los campos, cuando existan.Así que 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 el último (más allá de la clase get-sólo la propiedad)

Todos los no-propiedad abstracta de un setter.De lo contrario es inútil y que no debería de atención a usarlo.Microsoft no tiene que permiten hacer lo que quiera.Razón de ser:el setter existe en alguna forma o de otra, y puede lograr lo que desea Veerryy fácilmente.

La clase base, o de cualquier clase donde se puede leer una propiedad con {get;}, ha ALGUNOS tipo de expuestos setter de la propiedad.Los metadatos se parecerá a esto:

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

Pero la aplicació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; }
}

La Mayoría De Los Complejos:

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 manera, usted tiene el setter expuestos.En su caso, es posible que desee anular int Bar porque no quiero que la clase base para manejarlo, no tienen acceso a la revisión de cómo se está manejando, o tuvieron la tarea de hax código muy rápido n''dirty en contra de su voluntad.

En los dos Últimos, y el Ex (Conclusión)

Larga Historia Corta:No es necesario que Microsoft a cambio de nada.Usted puede elegir la forma de su clase de implementación que se establece y, sin el constructor, el uso de todos o ninguno de la clase base.

Solución para sólo un pequeño subconjunto de casos de uso, pero, sin embargo:en C# 6.0 "readonly" setter se agrega automáticamente anulado captador de propiedades de sólo.

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 la IL de nivel, una propiedad de lectura/escritura se traduce en dos (getter y setter) métodos.

Cuando se reemplaza, usted tiene que mantener el apoyo a los subyacentes de la interfaz.Si usted podría agregar un setter, que efectivamente podría ser la adición de un nuevo método, que seguiría siendo invisible para el mundo exterior, en cuanto a sus clases de la interfaz se refiere.

Cierto, la adición de un nuevo método no sería romper la compatibilidad de por sí, pero ya que iba a permanecer oculto, la decisión de no permitir que esto tiene perfecto sentido.

Debido a que rompiera el concepto de encapsulación y la implementación de la clandestinidad.Consideremos el caso cuando se crea una clase, hágalo y, a continuación, los consumidores de su clase que hace de sí mismo, capaz de establecer una propiedad para la que originalmente proporcionar un getter sólo.Efectivamente sería interrumpir cualquier invariantes de su clase que usted puede depender en su implementación.

Porque una clase que tiene una propiedad de sólo lectura (no setter) probablemente tiene una buena razón para ello.Puede que no haya ningún almacén de datos subyacente, por ejemplo.Permite crear un setter rompe el contrato se establece por la clase.Es simplemente mala programación orientada a objetos.

Una propiedad de sólo 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 de coincidencia de la (db)contexto de un objeto).Así que la responsabilidad de determinar el valor permanece dentro de la clase.

La adición de un setter causaría un incómodo problema aquí:Un error de validación debe producirse si se establece el valor a cualquier cosa a la que el único valor posible de lo que ya tiene.

Las reglas a menudo tienen excepciones, sin embargo.Es muy posible que, por ejemplo, en una clase derivada del contexto reduce los posibles valores de enumeración de hasta 3 de cada 10, sin embargo, el usuario de este objeto todavía necesita para decidir cuál es la correcta.La clase derivada debe delegar la responsabilidad de determinar el valor que el usuario de este objeto.Importante darse cuenta de es que el usuario de este objeto deben ser conscientes de esta excepción y asumir la responsabilidad para establecer el valor correcto.

Mi solución en este tipo de situaciones sería salir de la propiedad de sólo lectura y agregar una nueva propiedad de lectura y escritura para la clase derivada para apoyar la excepción.El reemplazo de la propiedad original simplemente devolver el valor de la nueva propiedad.La nueva propiedad puede tener un nombre propio que indica el contexto de esta excepción correctamente.

Esto también apoya la validez de la observación:"hacer que sea tan duro como sea posible para los malentendidos a los cultivos" por Gishu.

Esto no es imposible.Usted simplemente tiene que usar la "nueva" palabra clave 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 satisfaga a ambos lados de este debate.Mediante la opción "nuevo" se rompe el contrato entre la base de la implementación de la clase y la subclase de implementación.Esto es necesario cuando una Clase puede tener varios contratos (ya sea a través de la interfaz o clase base).

Espero que esto ayude

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top