Remplacement de champs ou de propriétés dans des sous-classes

145

J'ai une classe de base abstraite et je souhaite déclarer un champ ou une propriété qui aura une valeur différente dans chaque classe qui hérite de cette classe parente.

Je veux le définir dans la classe de base afin de pouvoir le référencer dans une méthode de classe de base - par exemple en remplaçant ToString pour dire "Cet objet est de type propriété / champ ". J'ai trois façons dont je peux voir de faire cela, mais je me demandais - quelle est la meilleure façon acceptée de faire cela? Question de débutant, désolé.

Option 1:
utilisez une propriété abstraite et remplacez-la sur les classes héritées. Cela bénéficie d'être appliqué (vous devez le remplacer) et c'est propre. Mais, il semble légèrement faux de renvoyer une valeur de code en dur plutôt que d'encapsuler un champ et il s'agit de quelques lignes de code au lieu de juste. Je dois aussi déclarer un corps pour "set" mais c'est moins important (et il y a probablement un moyen d'éviter ce que je ne connais pas).

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

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

Option 2
Je peux déclarer un champ public (ou un champ protégé) et le remplacer explicitement dans la classe héritée. L'exemple ci-dessous me donnera un avertissement pour utiliser "nouveau" et je peux probablement le faire, mais cela ne semble pas bien et cela brise le polymorphisme, ce qui était le point essentiel. Cela ne semble pas être une bonne idée ...

abstract class Mother
{
    public int MyInt = 0;
}

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

Option 3
Je peux utiliser un champ protégé et définir la valeur dans le constructeur. Cela semble assez ordonné mais dépend de moi pour que le constructeur définisse toujours cela et avec plusieurs constructeurs surchargés, il y a toujours une chance qu'un chemin de code ne définisse pas la valeur.

abstract class Aunt
{
    protected int MyInt;
}

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

C'est un peu une question théorique et je suppose que la réponse doit être l'option 1 car c'est la seule option sûre , mais je me suis juste familiarisé avec C # et je voulais poser cette question à des personnes ayant plus d'expérience.

Frans
la source
résumé public int MyInt {get; set;} => chaîne abstraite publique IntentName {get; set;}: D
Navid Golforoushan

Réponses:

136

Parmi les trois solutions, seule l' option 1 est polymorphe .

Les champs par eux-mêmes ne peuvent pas être remplacés. C'est exactement pourquoi l' option 2 renvoie l' avertissement de nouveau mot-clé.

La solution à l’avertissement n’est pas d’ajouter le mot-clé «nouveau», mais de mettre en œuvre l’option 1.

Si vous avez besoin que votre champ soit polymorphe, vous devez l'envelopper dans une propriété.

L'option 3 est OK si vous n'avez pas besoin d'un comportement polymorphe. Vous devez cependant vous rappeler que lors de l'accès à la propriété MyInt, la classe dérivée n'a aucun contrôle sur la valeur renvoyée. La classe de base seule est capable de renvoyer cette valeur.

Voici à quoi pourrait ressembler une implémentation véritablement polymorphe de votre propriété, permettant aux classes dérivées d'être en contrôle .

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 */ }
    }
}
Préréglages
la source
154
En passant, je pense vraiment que le Père devrait appliquer la formule «Y», et la Mère, logiquement, «X».
Peter - Réintègre Monica le
4
Que faire si je voulais fournir une implémentation par défaut dans Parent et qu'elle ne soit pas abstraite?
Aaron Franke
@AaronFranke Faites la signature: public virtual int MyInt {get; }
Ted Bigham
@ Peter-ReinstateMonica Ce serait de la "programmation génétique"
devinbost le
18

L'option 2 est non-starter - vous ne pouvez pas remplacer les champs, vous ne pouvez que les masquer .

Personnellement, je choisirais l'option 1 à chaque fois. J'essaie de garder les champs privés à tout moment. C'est si vous avez vraiment besoin de pouvoir remplacer la propriété, bien sûr. Une autre option consiste à avoir une propriété en lecture seule dans la classe de base qui est définie à partir d'un paramètre de constructeur:

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

C'est probablement l'approche la plus appropriée si la valeur ne change pas pendant la durée de vie de l'instance.

Jon Skeet
la source
Pouvons-nous dire que cela n'est pas correct sur la base de ce msdn.microsoft.com/en-us/library/9fkccyh4.aspx L'article msdn montre que vous pouvez remplacer les propriétés
codingbiz
1
@codingbiz: où ma réponse parle-t-elle des propriétés? Les champs et les propriétés ne sont pas la même chose.
Jon Skeet
@codingbiz: (Ma réponse parle maintenant des propriétés, certes - mais elle n'a jamais dit que vous ne pouviez pas les remplacer. Elle a dit - et dit - que vous ne pouvez pas remplacer les champs , ce qui est toujours correct.)
Jon Skeet
7

l'option 2 est une mauvaise idée. Il en résultera quelque chose appelé l'ombre; Fondamentalement, vous avez deux membres "MyInt" différents, l'un chez la mère et l'autre chez la fille. Le problème avec ceci, c'est que les méthodes qui sont implémentées dans la mère référenceront le "MyInt" de la mère tandis que les méthodes implémentées dans la fille feront référence au "MyInt" de la fille. cela peut causer de sérieux problèmes de lisibilité et de la confusion plus tard.

Personnellement, je pense que la meilleure option est 3; car il fournit une valeur centralisée claire et peut être référencé en interne par les enfants sans avoir à définir leurs propres champs - ce qui est le problème avec l'option 1.


la source
6

Tu pourrais faire ça

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 vous permet d'obtenir une propriété un corps qui fait quelque chose tout en laissant les sous-classes le remplacer.

Joshua G
la source
4

Vous pouvez définir quelque chose comme ceci:

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

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

En définissant la valeur en lecture seule, cela garantit que la valeur de cette classe reste inchangée pendant toute la durée de vie de l'objet.

Je suppose que la question suivante est: pourquoi en avez-vous besoin?

Fourmi
la source
Static est un mauvais choix de mots car cela implique que la valeur est ensuite partagée entre toutes les instances de la classe, ce qui bien sûr n'est pas.
Winston Smith
3

Si vous créez une classe et que vous souhaitez qu'il y ait une valeur de base pour la propriété, utilisez le virtualmot - clé dans la classe de base. Cela vous permet de remplacer éventuellement la propriété.

En utilisant votre exemple ci-dessus:

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

Dans un programme:

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

J'irais avec l'option 3, mais j'ai une méthode abstraite setMyInt que les sous-classes sont obligées de mettre en œuvre. De cette façon, vous n'aurez pas le problème d'une classe dérivée oubliant de la définir dans le constructeur.

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

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

Au fait, avec l'option un, si vous ne spécifiez pas set; dans votre propriété de classe de base abstraite, la classe dérivée n'aura pas à l'implémenter.

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

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

Vous pouvez opter pour l'option 3 si vous modifiez votre classe de base abstraite pour exiger la valeur de propriété dans le constructeur, vous ne manquerez aucun chemin. J'envisagerais vraiment cette option.

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

}

Bien sûr, vous avez alors toujours la possibilité de rendre le champ privé puis, en fonction des besoins, d'exposer un getter de propriété protégée ou publique.

Blair Conrad
la source
0

J'ai fait ça...

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 cette façon, vous pouvez toujours utiliser des champs.

8r13n
la source
J'ai l'impression que c'est une solution ad hoc pour le prototypage ou la démonstration.
Zimano
0

L'exemple d'implémentation lorsque vous souhaitez avoir une classe abstraite avec implémentation. Les sous-classes doivent:

  1. Paramétrez l'implémentation d'une classe abstraite.
  2. Hérite complètement de l'implémentation de la classe abstraite;
  3. Ayez votre propre implémentation.

Dans ce cas, les propriétés nécessaires à l'implémentation ne doivent pas être disponibles pour une utilisation sauf pour la classe abstraite et sa propre sous-classe.

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