Подтвердить что ты не робот

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

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

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

Модификатор "virtual" недействителен для этого элемента

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

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

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

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

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

код:

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);
        }
    }
}
4b9b3361

Ответ 1

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

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

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

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

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

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

// 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:. Вы также спросили, какая разница между виртуальным/переопределением и скрытием имени (новое ключевое слово в методах), если этого следует избегать, и если это может быть полезно.

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Primary: name1, state1 
Primary: name1, state1

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

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

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

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 и нуждаетесь в установленном значении, вам, вероятно, придется изменить транзакцию, возможно, вот так:

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

class Animal{

    public class Head {
        int eye = 8;
    } 
    Head head = new Head()
    public void Test() 
    { 
        print head.eye; 
        DoOtherStufs(); 
    } //8

    protected virtual void DoOtherStufs() 
    {}
}

class Cat : Animal{
    class new Head : Animal.Head 
    {
        int mouth;
    } 
    Head head = new Head()
    protected override void DoOtherStufs() 
    { 
        print head.eye; 
    }
} 
Cat cat = new Cat();

cat.head.eye = 9;
print cat.Test() 

Это напечатает 8 9 вместо 9 9, как и ожидалось. Мне нужен функционал базового класса, но мне также нужен унаследованный класс, с помощью которого я мог бы манипулировать (увеличивать) переменными внутри классов внутренней группы. Это невозможно!!