Почему я не могу использовать виртуальные / переопределить переменные класса, как я могу на методах? - PullRequest
12 голосов
/ 04 марта 2010

В следующем примере я могу создать виртуальный метод Show() в унаследованном классе и затем переопределить его в наследующем класс.

Я хочу сделать то же самое с защищенной переменной класса prefix, но я получаю ошибку:

Недопустимый модификатор virtual за этот товар

Но так как я не могу определить эту переменную как virtual / override в моих классах, я получаю компилятор warning :

TestOverride234355.SecondaryTransaction.prefix» скрывает унаследованный член 'TestOverride234355.Transaction.prefix. Используйте новое ключевое слово, если скрытие было предназначенный.

К счастью, когда я добавляю ключевое слово new, все работает отлично , что нормально, так как я получаю ту же функциональность, но возникает два вопроса :

  1. Почему я могу использовать виртуальный / переопределение для методов, но не для переменных защищенного класса?

  2. Какая разница на самом деле между подходом виртуального / переопределения и подходом "скрыть это с новым", поскольку, по крайней мере, в этом примере они предлагают одинаковую функциональность?

Код:

using System;

namespace TestOverride234355
{
    public class Program
    {
        static void Main(string[] args)
        {
            Transaction st1 = new Transaction { Name = "name1", State = "state1" };
            SecondaryTransaction st2 = 
                new SecondaryTransaction { Name = "name1", State = "state1" };

            Console.WriteLine(st1.Show());
            Console.WriteLine(st2.Show());

            Console.ReadLine();
        }
    }

    public class Transaction
    {
        public string Name { get; set; }
        public string State { get; set; }

        protected string prefix = "Primary";

        public virtual string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }

    public class SecondaryTransaction : Transaction
    {
        protected new string prefix = "Secondary";

        public override string Show()
        {
            return String.Format("{0}: {1}, {2}", prefix, Name, State);
        }
    }
}

Ответы [ 7 ]

8 голосов
/ 04 марта 2010

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

В вашем случае вы могли бы установить prefix в конструкторе для наследующего класса:

// Base class field declaration and constructor
protected string prefix;

public Transaction()
{
  prefix = "Primary";
}

// Child class constructor
public SecondaryTransaction()
{
  prefix = "Secondary";
}

Вы также можете создать свойство вместо поля и сделать свойство виртуальным. Это позволит вам изменить поведение метода получения и установки свойства в наследующем классе:

// Base class
public virtual string Prefix { get { /* ... */ } set { /* ... */ } }

// Child class
public override string Prefix { get { /* ... */ } set { /* ... */ } }

РЕДАКТИРОВАТЬ: Что касается вашего вопроса об использовании переменной в базовом конструкторе до того, как наследующий класс установил ее, один из способов решить это - определить метод инициализации в базовом классе, переопределить его в наследующий класс, и вызовите его из базового конструктора перед тем, как получить доступ к любым полям:

// Base class
public class Base
{
  protected string prefix;

  public Base()
  {
    Initialize();
    Console.WriteLine(prefix);
  }  

  protected virtual void Initialize()
  {
    prefix = "Primary";
  }
}

// Inheriting class
public class Child : Base
{
  public override void Initialize()
  {
    prefix = "Secondary";
  }
}

РЕДАКТИРОВАНИЕ 2: Вы также спросили, в чем разница между виртуальным / переопределением и сокрытием имени (новое ключевое слово для методов), следует ли этого избегать и может ли оно быть полезным.

Скрытие имени - это функция, которая нарушает наследование в случае скрытия виртуальных методов. То есть, если вы прячете метод Initialize() в дочернем классе, базовый класс его не увидит и не вызовет. Кроме того, если бы метод Initialize() был общедоступным, внешний код, который вызывал Initialize() для ссылки базового типа, вызывал бы Initialize() для базового типа.

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

3 голосов
/ 04 марта 2010

Скорее создайте свойство для префиксного члена - так вы можете установить свойство виртуальным / абстрактным

Поля используются для хранения состояния объекта, они помогают объекту инкапсулировать данные и скрывают проблемы реализации от других. Имея возможность переопределить поле, мы просачиваем проблемы реализации класса в клиентский код (включая подтипы). Из-за этого большинство языков приняли решение, что нельзя определять переменные экземпляра, которые могут быть переопределены (хотя они могут быть публичными / защищенными ... так что вы можете получить к ним доступ).

Вы также не можете поместить переменные экземпляра в интерфейс

2 голосов
/ 04 марта 2010

Статический или не виртуальный метод или свойство - это просто адрес памяти (для упрощения). Виртуальный метод или свойство идентифицируется записью в таблице. Эта таблица зависит от класса, определяющего метод или свойство. Когда вы переопределяете виртуальный член в производном классе, вы фактически изменяете запись в таблице, чтобы производный класс указывал на переопределяющий метод. Во время выполнения доступ к такому члену осуществляется через таблицу всегда. Таким образом, запись может быть переопределена любым производным классом.

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

Использование 'new' для члена означает, что вы не хотите переопределять запись в таблице, но хотите, чтобы вы выбрали нового участника (с тем же именем, что и у существующего виртуального, плохая практика, если вы спросите меня) .

Если вы обращаетесь к виртуальному члену через указатель на базовый класс, вы никогда не получите доступ к члену, определенному как «новый» в производном классе, что является отличием, упомянутым во второй части вашего вопроса.

1 голос
/ 04 марта 2010

В вашем примере, если вы не переопределите «Show» в классе SecondaryTransaction, то вызов Show для экземпляра SecondaryTransaction фактически вызовет метод в базовом классе (Transaction), который, следовательно, будет использовать «Show» в базовом классе, в результате чего получается:

Primary: name1, state1 
Primary: name1, state1

Таким образом, в зависимости от того, какой метод вы вызываете (т. Е. Один для базового класса или дочернего класса), код будет иметь другое значение для «префикса», что будет обслуживаемостью кошмар . Я подозреваю, что вы, вероятно, хотите / должны сделать, выставить свойство в транзакции, которое заключает в себе «префикс».

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

Что бы я сделал (если бы я абсолютно не хотел / не мог использовать свойства):

public class Transaction
{
    public string Name { get; set; }
    public string State { get; set; }
    protected string prefix = "Primary";
    public virtual string Show()
    {
        return String.Format("{0}: {1}, {2}", prefix, Name, State);
    }
}
public class SecondaryTransaction : Transaction
{ 
    public SecondaryTransaction()
    {
        prefix = "Secondary";
    }
    public override string Show()
    {
        return String.Format("{0}: {1}, {2}", prefix, Name, State);
    }
}

Редактировать: (согласно моему комментарию к другому ответу)

Если вы вызываете ctor вашего базового класса и вам нужно установить значение, вам, вероятно, придется изменить Transaction, возможно, так:

public class Transaction
{
    public string Name { get; set; }
    public string State { get; set; }
    protected string prefix = "Primary";
    // Declared as virtual ratther than abstract to avoid having to implement "TransactionBase"
    protected virtual void Initialise()
    { }
    public Transaction()
    {
        Initialise();
    }
    public virtual string Show()
    {
        return String.Format("{0}: {1}, {2}", prefix, Name, State);
    }
}
public class SecondaryTransaction : Transaction
{ 
    protected override void Initialise()
    {
        prefix = "Secondary";
    }
    public override string Show()
    {
        return String.Format("{0}: {1}, {2}", prefix, Name, State);
    }
}
0 голосов
/ 04 марта 2010

Переопределение поля - нонсенс. Отметив поле как защищенное, вы автоматически можете обращаться к ним в производных классах. Вы можете переопределить функции, свойства, потому что он использует функции внутри.

0 голосов
/ 04 марта 2010

Вы не можете, потому что это бесполезно. Что бы вы сделали, переопределив поле?

0 голосов
/ 04 марта 2010

Почему вы хотите переопределить защищенную переменную, конечно же, все, что вам нужно сделать, - это установить ее в другое значение в переопределяющем классе (возможно, в конструкторе)?

...