Переопределение полей или свойств в подклассах


145

У меня есть абстрактный базовый класс, и я хочу объявить поле или свойство, которое будет иметь разные значения в каждом классе, который наследуется от этого родительского класса.

Я хочу определить его в базовом классе, чтобы я мог ссылаться на него в методе базового класса - например, переопределив ToString, чтобы сказать «Этот объект имеет свойство / поле типа ». У меня есть три способа сделать это, но мне было интересно - как лучше всего это сделать? Вопрос новичка, извините.

Вариант 1:
использовать абстрактное свойство и переопределить его в унаследованных классах. Это полезно от принудительного применения (вы должны переопределить его), и оно чисто. Но кажется немного неправильным возвращать значение жесткого кода, а не инкапсулировать поле, и это всего лишь несколько строк кода, а не просто. Я также должен объявить тело для «set», но это менее важно (и, вероятно, есть способ избежать того, о чем я не знаю).

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

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

Вариант 2
Я могу объявить открытое поле (или защищенное поле) и явно переопределить его в унаследованном классе. Приведенный ниже пример предупредит меня об использовании «new», и я, вероятно, могу это сделать, но он кажется неправильным и нарушает полиморфизм, что и было всем. Не похоже на хорошую идею ...

abstract class Mother
{
    public int MyInt = 0;
}

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

Вариант 3
Я могу использовать защищенное поле и установить значение в конструкторе. Это выглядит довольно аккуратно, но я полагаюсь на то, что конструктор всегда устанавливает это, а при наличии нескольких перегруженных конструкторов всегда есть вероятность, что путь кода не установит значение.

abstract class Aunt
{
    protected int MyInt;
}

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

Это немного теоретический вопрос, и я думаю, что ответ должен быть вариант 1, поскольку это единственный безопасный вариант, но я только начинаю разбираться с C # и хотел спросить это у людей с большим опытом.


abstract public int MyInt {get; set;} => публичная абстрактная строка IntentName {get; set;}: D
Навид Голфороушан

Ответы:


136

Из трех решений только вариант 1 является полиморфным .

Поля сами по себе не могут быть переопределены. Именно поэтому Вариант 2 возвращает новый предупреждение ключевом слове.

Решение для предупреждения заключается не в добавлении ключевого слова «new», а в реализации варианта 1.

Если вам нужно, чтобы ваше поле было полиморфным, вам нужно обернуть его в свойстве.

Вариант 3 в порядке, если вам не нужно полиморфное поведение. Однако следует помнить, что при доступе к свойству MyInt производный класс не контролирует возвращаемое значение. Базовый класс сам по себе способен возвращать это значение.

Вот как может выглядеть по-настоящему полиморфная реализация вашего свойства, позволяющая управлять производными классами .

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
Кроме того, я действительно думаю, что Отец должен применять формулу «Y», а Мать, логически, «X».
Питер - Восстановить Монику

4
Что, если я хотел бы предоставить реализацию по умолчанию в Parent, чтобы она не была абстрактной?
Аарон Франке

@AaronFranke Сделайте подпись: public virtual int MyInt {get; }
Тед Бигхам

@ Peter-ReinstateMonica Это было бы "генетическое программирование"
devinbost

18

Вариант 2 не является начальным - вы не можете переопределить поля, вы можете только скрыть их.

Лично я бы выбрал вариант 1 каждый раз. Я стараюсь всегда держать поля закрытыми. Это, если вам действительно нужно иметь возможность переопределить свойство вообще, конечно. Другой вариант - иметь свойство только для чтения в базовом классе, которое устанавливается из параметра конструктора:

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

Вероятно, это наиболее подходящий подход, если значение не меняется в течение срока действия экземпляра.


Можем ли мы сказать, что сейчас это неверно, основываясь на этом msdn.microsoft.com/en-us/library/9fkccyh4.aspx В статье msdn показано, что вы можете переопределять свойства
codingbiz

1
@codingbiz: где мой ответ говорит о свойствах? Поля и свойства не одно и то же.
Джон Скит

@codingbiz: (Мой ответ теперь говорит о свойствах, по общему признанию - но он никогда не говорил, что вы не можете переопределить их. Он сказал - и говорит - что вы не можете переопределить поля , что все еще правильно.)
Джон Скит,

7

Вариант 2 плохая идея. Это приведет к тому, что называется затенением; По сути, у вас есть два разных члена «MyInt», один в матери, а другой в дочери. Проблема в том, что методы, которые реализованы в матери, будут ссылаться на «MyInt» матери, в то время как методы, реализованные в дочери, будут ссылаться на «MyInt» дочери. это может привести к серьезным проблемам с читабельностью и путанице в дальнейшем.

Лично я думаю, что лучший вариант - 3; потому что он обеспечивает четкое централизованное значение, и на него могут ссылаться дети, не стесняясь определять свои собственные поля - что является проблемой с вариантом 1.


6

Вы могли бы сделать это

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

Виртуальный позволяет получить свойство - тело, которое что-то делает, и все же позволяет подклассам переопределять его.


4

Вы можете определить что-то вроде этого:

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

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

Устанавливая значение только для чтения, оно гарантирует, что значение для этого класса останется неизменным в течение всего времени существования объекта.

Полагаю, следующий вопрос: зачем тебе это?


Static - плохой выбор слов, поскольку он подразумевает, что значение затем распределяется между всеми экземплярами класса, что, конечно, не так.
Уинстон Смит

3

Если вы создаете класс и хотите, чтобы для свойства существовало базовое значение, используйте virtualключевое слово в базовом классе. Это позволяет вам при необходимости переопределить свойство.

Используя ваш пример выше:

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

В программе:

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

0

Я бы выбрал вариант 3, но у меня есть абстрактный метод setMyInt, который подклассы вынуждены реализовывать. Таким образом, у вас не будет проблемы с производным классом, забывшим установить его в конструкторе.

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

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

Кстати, с первым вариантом, если вы не указали set; в вашем абстрактном свойстве базового класса производный класс не должен будет его реализовывать.

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

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

0

Вы можете перейти с опцией 3, если вы измените свой абстрактный базовый класс, чтобы требовать значение свойства в конструкторе, вы не пропустите ни одного пути. Я бы действительно рассмотрел этот вариант.

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

}

Конечно, тогда у вас все еще есть возможность сделать поле частным, а затем, в зависимости от необходимости, открыть защищенный или получатель публичной собственности.


0

Я сделал это...

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

Таким образом, вы все еще можете использовать поля.


Я чувствую, что это хорошо как специальное решение для прототипирования или демонстрации.
Зимано

0

Пример реализации, когда вы хотите иметь абстрактный класс с реализацией. Подклассы должны:

  1. Параметризация реализации абстрактного класса.
  2. Полностью наследовать реализацию абстрактного класса;
  3. Имейте свою собственную реализацию.

В этом случае свойства, необходимые для реализации, не должны быть доступны для использования, за исключением абстрактного класса и его собственного подкласса.

    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
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.