Anulación de campos o propiedades en subclases

145

Tengo una clase base abstracta y quiero declarar un campo o una propiedad que tendrá un valor diferente en cada clase que herede de esta clase principal.

Quiero definirlo en la clase base para poder hacer referencia a él en un método de clase base, por ejemplo, anulando ToString para decir "Este objeto es de tipo propiedad / campo ". Tengo tres formas de ver esto, pero me preguntaba: ¿cuál es la mejor forma aceptada de hacerlo? Pregunta de novato, lo siento.

Opción 1:
usar una propiedad abstracta y anularla en las clases heredadas. Esto se beneficia de su cumplimiento (debe anularlo) y está limpio. Pero, se siente un poco mal devolver un valor de código duro en lugar de encapsular un campo y son unas pocas líneas de código en lugar de solo. También tengo que declarar un cuerpo para "set", pero eso es menos importante (y probablemente haya una forma de evitar lo que no conozco).

abstract class Father
{
    abstract public int MyInt { get; set;}
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
        set { }
    }
}

Opción 2
Puedo declarar un campo público (o un campo protegido) y anularlo explícitamente en la clase heredada. El siguiente ejemplo me dará una advertencia para usar "nuevo" y probablemente pueda hacerlo, pero se siente mal y rompe el polimorfismo, que era el punto central. No parece una buena idea ...

abstract class Mother
{
    public int MyInt = 0;
}

class Daughter : Mother
{
    public int MyInt = 1;
}

Opción 3
Puedo usar un campo protegido y establecer el valor en el constructor. Esto parece bastante ordenado, pero confía en mí para garantizar que el constructor siempre establezca esto y, con múltiples constructores sobrecargados, siempre existe la posibilidad de que alguna ruta de código no establezca el valor.

abstract class Aunt
{
    protected int MyInt;
}

class Niece : Aunt
{
    public Niece()
    {
        MyInt = 1;
    }
}

Es una pregunta un poco teórica y supongo que la respuesta tiene que ser la opción 1, ya que es la única opción segura , pero me estoy familiarizando con C # y quería preguntarle esto a las personas con más experiencia.

Frans
fuente
abstract public int MyInt {get; set;} => cadena abstracta pública IntentName {get; set;}: D
Navid Golforoushan

Respuestas:

136

De las tres soluciones, solo la Opción 1 es polimórfica .

Los campos por sí mismos no pueden ser anulados. Por eso exactamente la Opción 2 devuelve la nueva advertencia de palabra clave.

La solución a la advertencia no es agregar la palabra clave "nueva", sino implementar la Opción 1.

Si necesita que su campo sea polimórfico, debe envolverlo en una Propiedad.

La opción 3 está bien si no necesita un comportamiento polimórfico. Sin embargo, debe recordar que cuando en tiempo de ejecución se accede a la propiedad MyInt, la clase derivada no tiene control sobre el valor devuelto. La clase base por sí misma es capaz de devolver este valor.

Así es como podría verse una implementación verdaderamente polimórfica de su propiedad, permitiendo que las clases derivadas tengan el control .

abstract class Parent
{
    abstract public int MyInt { get; }
}

class Father : Parent
{
    public override int MyInt
    {
        get { /* Apply formula "X" and return a value */ }
    }
}

class Mother : Parent
{
    public override int MyInt
    {
        get { /* Apply formula "Y" and return a value */ }
    }
}
Calles
fuente
154
Como comentario, realmente creo que el Padre debería aplicar la fórmula "Y", y la Madre, lógicamente, "X".
Peter - Restablece a Mónica el
44
¿Qué sucede si quisiera proporcionar una implementación predeterminada en Parent y no fuera abstracta?
Aaron Franke
@AaronFranke Hacer la firma: public virtual int MyInt {get; }
Ted Bigham
@ Peter-ReinstateMonica Eso sería "programación genética"
devinbost
18

La opción 2 no es un iniciador: no puede anular campos, solo puede ocultarlos .

Personalmente, iría por la opción 1 cada vez. Intento mantener los campos privados en todo momento. Eso si realmente necesita poder anular la propiedad, por supuesto. Otra opción es tener una propiedad de solo lectura en la clase base que se establece a partir de un parámetro constructor:

abstract class Mother
{
    private readonly int myInt;
    public int MyInt { get { return myInt; } }

    protected Mother(int myInt)
    {
        this.myInt = myInt;
    }
}

class Daughter : Mother
{
    public Daughter() : base(1)
    {
    }
}

Ese es probablemente el enfoque más apropiado si el valor no cambia durante la vida útil de la instancia.

Jon Skeet
fuente
¿Podemos decir que esto ahora no es correcto en base a esto msdn.microsoft.com/en-us/library/9fkccyh4.aspx? El artículo de msdn muestra que puede anular las propiedades
codingbiz
1
@codingbiz: ¿dónde habla mi respuesta sobre las propiedades? Los campos y las propiedades no son lo mismo.
Jon Skeet
@codingbiz: (Mi respuesta ahora habla de las propiedades, sin duda, pero nunca dijo que no podía anularlas. Dijo, y dice, que no puede anular los campos , lo que sigue siendo correcto.)
Jon Skeet
7

La opción 2 es una mala idea. Resultará en algo llamado sombreado; Básicamente tiene dos miembros diferentes de "MyInt", uno en la madre y el otro en la hija. El problema con esto es que los métodos implementados en la madre harán referencia al "MyInt" de la madre, mientras que los métodos implementados en la hija harán referencia al "MyInt" de la hija. Esto puede causar algunos problemas serios de legibilidad y confusión más adelante.

Personalmente, creo que la mejor opción es 3; porque proporciona un valor centralizado claro y los niños pueden hacer referencia internamente sin la molestia de definir sus propios campos, que es el problema con la opción 1.


fuente
6

Podrías hacer esto

class x
{
    private int _myInt;
    public virtual int myInt { get { return _myInt; } set { _myInt = value; } }
}

class y : x
{
    private int _myYInt;
    public override int myInt { get { return _myYInt; } set { _myYInt = value; } }
}

virtual le permite obtener una propiedad de un cuerpo que hace algo y aún permite que las subclases lo anulen.

Joshua G
fuente
4

Podrías definir algo como esto:

abstract class Father
{
    //Do you need it public?
    protected readonly int MyInt;
}

class Son : Father
{
    public Son()
    {
        MyInt = 1;
    }
}

Al establecer el valor como de solo lectura, se garantiza que el valor de esa clase permanezca sin cambios durante la vida útil del objeto.

Supongo que la siguiente pregunta es: ¿por qué lo necesitas?

Hormiga
fuente
Estático es una mala elección de palabras, ya que implica que el valor se comparte entre todas las instancias de la clase, que por supuesto no lo es.
Winston Smith
3

Si está creando una clase y desea que haya un valor base para la propiedad, utilice la virtualpalabra clave en la clase base. Esto le permite anular opcionalmente la propiedad.

Usando su ejemplo anterior:

//you may want to also use interfaces.
interface IFather
{
    int MyInt { get; set; }
}


public class Father : IFather
{
    //defaulting the value of this property to 1
    private int myInt = 1;

    public virtual int MyInt
    {
        get { return myInt; }
        set { myInt = value; }
    }
}

public class Son : Father
{
    public override int MyInt
    {
        get {

            //demonstrating that you can access base.properties
            //this will return 1 from the base class
            int baseInt = base.MyInt;

            //add 1 and return new value
            return baseInt + 1;
        }
        set
        {
            //sets the value of the property
            base.MyInt = value;
        }
    }
}

En un programa:

Son son = new Son();
//son.MyInt will equal 2
Keith Aymar
fuente
0

Iría con la opción 3, pero tengo un método abstracto setMyInt que las subclases están obligadas a implementar. De esta forma, no tendrá el problema de que una clase derivada se olvide de configurarla en el constructor.

abstract class Base 
{
 protected int myInt;
 protected abstract void setMyInt();
}

class Derived : Base 
{
 override protected void setMyInt()
 {
   myInt = 3;
 }
}

Por cierto, con la opción uno, si no especifica set; en su propiedad de clase base abstracta, la clase derivada no tendrá que implementarla.

abstract class Father
{
    abstract public int MyInt { get; }
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
    }
}
Winston Smith
fuente
0

Puede ir con la opción 3 si modifica su clase base abstracta para requerir el valor de la propiedad en el constructor, no se perderá ninguna ruta. Realmente consideraría esta opción.

abstract class Aunt
{
    protected int MyInt;
    protected Aunt(int myInt)
    {
        MyInt = myInt;
    }

}

Por supuesto, aún tiene la opción de hacer que el campo sea privado y luego, dependiendo de la necesidad, exponer a un comprador protegido o de propiedad pública.

Blair Conrad
fuente
0

Hice esto...

namespace Core.Text.Menus
{
    public abstract class AbstractBaseClass
    {
        public string SELECT_MODEL;
        public string BROWSE_RECORDS;
        public string SETUP;
    }
}

namespace Core.Text.Menus
{
    public class English : AbstractBaseClass
    {
        public English()
        {
            base.SELECT_MODEL = "Select Model";
            base.BROWSE_RECORDS = "Browse Measurements";
            base.SETUP = "Setup Instrument";
        }
    }
}

De esta manera, todavía puede usar campos.

8r13n
fuente
Siento que esto es bueno como una solución ad-hoc para la creación de prototipos o la demostración.
Zimano
0

La implementación de ejemplo cuando desea tener una clase abstracta con implementación. Las subclases deben:

  1. Parametrizar la implementación de una clase abstracta.
  2. Herede completamente la implementación de la clase abstracta;
  3. Ten tu propia implementación.

En este caso, las propiedades que son necesarias para la implementación no deberían estar disponibles para su uso, excepto para la clase abstracta y su propia subclase.

    internal abstract class AbstractClass
    {
        //Properties for parameterization from concrete class
        protected abstract string Param1 { get; }
        protected abstract string Param2 { get; }

        //Internal fields need for manage state of object
        private string var1;
        private string var2;

        internal AbstractClass(string _var1, string _var2)
        {
            this.var1 = _var1;
            this.var2 = _var2;
        }

        internal void CalcResult()
        {
            //The result calculation uses Param1, Param2, var1, var2;
        }
    }

    internal class ConcreteClassFirst : AbstractClass
    {
        private string param1;
        private string param2;
        protected override string Param1 { get { return param1; } }
        protected override string Param2 { get { return param2; } }

        public ConcreteClassFirst(string _var1, string _var2) : base(_var1, _var2) { }

        internal void CalcParams()
        {
            //The calculation param1 and param2
        }
    }

    internal class ConcreteClassSecond : AbstractClass
    {
        private string param1;
        private string param2;

        protected override string Param1 { get { return param1; } }

        protected override string Param2 { get { return param2; } }

        public ConcreteClassSecond(string _var1, string _var2) : base(_var1, _var2) { }

        internal void CalcParams()
        {
            //The calculation param1 and param2
        }
    }

    static void Main(string[] args)
    {
        string var1_1 = "val1_1";
        string var1_2 = "val1_2";

        ConcreteClassFirst concreteClassFirst = new ConcreteClassFirst(var1_1, var1_2);
        concreteClassFirst.CalcParams();
        concreteClassFirst.CalcResult();

        string var2_1 = "val2_1";
        string var2_2 = "val2_2";

        ConcreteClassSecond concreteClassSecond = new ConcreteClassSecond(var2_1, var2_2);
        concreteClassSecond.CalcParams();
        concreteClassSecond.CalcResult();

        //Param1 and Param2 are not visible in main method
    }
Belomestnykh Sergey
fuente