Substituindo campos ou propriedades nas subclasses


145

Eu tenho uma classe base abstrata e quero declarar um campo ou uma propriedade que terá um valor diferente em cada classe que herda dessa classe pai.

Eu quero defini-lo na classe base para que eu possa referenciá-lo em um método de classe base - por exemplo, substituindo ToString para dizer "Este objeto é do tipo propriedade / campo ". Eu tenho três maneiras que eu posso ver para fazer isso, mas eu estava pensando - qual é a melhor ou mais aceita maneira de fazer isso? Pergunta para iniciantes, desculpe.

Opção 1:
use uma propriedade abstrata e substitua-a nas classes herdadas. Isso se beneficia ao ser aplicado (é necessário substituí-lo) e é limpo. Mas, parece um pouco errado retornar um valor de código rígido em vez de encapsular um campo e são algumas linhas de código em vez de apenas. Eu também tenho que declarar um corpo como "definido", mas isso é menos importante (e provavelmente existe uma maneira de evitar aquilo de que não estou ciente).

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

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

Opção 2
Posso declarar um campo público (ou um campo protegido) e substituí-lo explicitamente na classe herdada. O exemplo abaixo me dará um aviso para usar "novo" e provavelmente posso fazer isso, mas parece errado e quebra o polimorfismo, que era o ponto principal. Não parece uma boa ideia ...

abstract class Mother
{
    public int MyInt = 0;
}

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

Opção 3
Eu posso usar um campo protegido e definir o valor no construtor. Isso parece bem arrumado, mas depende de eu garantir que o construtor sempre defina isso e com vários construtores sobrecarregados, sempre haja uma chance de que algum caminho de código não defina o valor.

abstract class Aunt
{
    protected int MyInt;
}

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

É um pouco de uma questão teórica e acho que a resposta deve ser a opção 1, pois é a única opção segura, mas estou começando a entender o C # e queria perguntar isso a pessoas com mais experiência.


resumo público int MyInt {get; set;} => sequência abstrata pública IntentName {get; set;}: D
Navid Golforoushan

Respostas:


136

Das três soluções, apenas a opção 1 é polimórfica .

Os campos por si só não podem ser substituídos. É exatamente por isso que a opção 2 retorna o novo aviso de palavra-chave.

A solução para o aviso não é anexar a palavra-chave “nova”, mas implementar a Opção 1.

Se você precisa que seu campo seja polimórfico, é necessário envolvê-lo em uma Propriedade.

A opção 3 é válida se você não precisar de comportamento polimórfico. Você deve se lembrar, porém, que quando em tempo de execução a propriedade MyInt é acessada, a classe derivada não tem controle sobre o valor retornado. A classe base por si só é capaz de retornar esse valor.

É assim que uma implementação verdadeiramente polimórfica de sua propriedade pode parecer, permitindo que as classes derivadas estejam no controle .

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

154
Como um aparte, eu realmente acho que o Pai deveria aplicar a fórmula "Y", e a Mãe, logicamente, "X".
Peter - Restabelece Monica

4
E se eu quisesse fornecer uma implementação padrão no Parent e não ser abstrata?
Aaron Franke

@AaronFranke Faça a assinatura: public virtual int MyInt {get; }
Ted Bigham

@ Peter-ReinstateMonica Isso seria "programação genética"
devinbost 26/06

18

A opção 2 não é iniciada - você não pode substituir os campos, só pode ocultá- los.

Pessoalmente, eu optaria pela opção 1 todas as vezes. Eu tento manter os campos privados o tempo todo. Isso se você realmente precisar substituir a propriedade, é claro. Outra opção é ter uma propriedade somente leitura na classe base, que é configurada a partir de um parâmetro do construtor:

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

Essa é provavelmente a abordagem mais apropriada se o valor não mudar ao longo da vida útil da instância.


Podemos dizer isso agora não é correto com base nesta msdn.microsoft.com/en-us/library/9fkccyh4.aspx O artigo mostra MSDN você pode substituir propriedades
codingbiz

1
@codingbiz: onde minha resposta fala sobre propriedades? Campos e propriedades não são a mesma coisa.
Jon Skeet

@codingbiz: (Minha resposta agora fala sobre propriedades, é certo - mas nunca disse que você não podia substituí-las. Dizia - e diz - que você não pode substituir campos , o que ainda está correto.)
Jon Skeet,

7

a opção 2 é uma má ideia. Isso resultará em algo chamado sombreamento; Basicamente, você tem dois membros "MyInt" diferentes, um na mãe e outro na filha. O problema é que os métodos implementados na mãe farão referência ao "MyInt" da mãe, enquanto os métodos implementados na filha farão referência ao "MyInt" da filha. isso pode causar sérios problemas de legibilidade e confusão posteriormente.

Pessoalmente, acho que a melhor opção é 3; porque fornece um valor centralizado claro e pode ser referenciado internamente por crianças sem o incômodo de definir seus próprios campos - que é o problema da opção 1.


6

Você poderia fazer isso

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 permite que você obtenha uma propriedade de um corpo que faz algo e ainda permite que as subclasses a substituam.


4

Você pode definir algo como isto:

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

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

Ao definir o valor como somente leitura, ele garante que o valor dessa classe permaneça inalterado durante a vida útil do objeto.

Suponho que a próxima pergunta seja: por que você precisa disso?


Static é uma má escolha de palavras, pois implica que o valor será compartilhado entre todas as instâncias da classe, o que, obviamente, não é.
Winston Smith

3

Se você está construindo uma classe e deseja que exista um valor base para a propriedade, use a virtualpalavra - chave na classe base. Isso permite que você substitua opcionalmente a propriedade.

Usando seu exemplo acima:

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

Em um programa:

Son son = new Son();
//son.MyInt will equal 2

0

Eu iria com a opção 3, mas teria um método setMyInt abstrato que as subclasses são forçadas a implementar. Dessa forma, você não terá o problema de uma classe derivada esquecer de defini-la no construtor.

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

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

A propósito, com a opção um, se você não especificar set; na propriedade abstrata da classe base, a classe derivada não precisará implementá-la.

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

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

0

Você pode usar a opção 3 se modificar sua classe base abstrata para exigir o valor da propriedade no construtor, não perderá nenhum caminho. Eu realmente consideraria essa opção.

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

}

Obviamente, você ainda tem a opção de tornar o campo privado e, dependendo da necessidade, expor um getter de propriedade pública ou protegida.


0

Eu fiz isso...

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

Dessa forma, você ainda pode usar campos.


Eu sinto que isso é bom como uma solução ad-hoc para prototipagem ou demonstração.
Zimano 11/08/19

0

O exemplo de implementação quando você deseja ter uma classe abstrata com implementação. As subclasses devem:

  1. Parametrize a implementação de uma classe abstrata.
  2. Herdar completamente a implementação da classe abstrata;
  3. Tenha sua própria implementação.

Nesse caso, as propriedades necessárias para a implementação não devem estar disponíveis para uso, exceto para a classe abstrata e sua própria subclasse.

    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
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.