В следующем примере я в состоянии создать виртуальный метод Show()
в наследованном классе и затем переопределяют его в наследующем классе.
Я хочу сделать то же самое с переменной защищенного класса prefix
но я получаю ошибку:
'Виртуальный' модификатор не допустим для этого объекта
Но так как я не могу определить эту переменную как виртуальную / переопределение в моих классах, я получаю предупреждение компилятора:
TestOverride234355. SecondaryTransaction.prefix' скрывает наследованного участника 'TestOverride234355. Transaction.prefix'. Используйте новое ключевое слово, если сокрытие было предназначено.
К счастью, когда я добавляю new
ключевое слово все хорошо работает, который в порядке, так как я получаю ту же функциональность, но это поднимает два вопроса:
Почему я могу использовать виртуальный / переопределение для методов, но не для переменных защищенного класса?
Каково различие на самом деле между виртуальным подходом / подходом переопределения и подходом hide-it-with-new с тех пор, по крайней мере, в этом примере, который они предлагают той же функциональности?
Код:
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);
}
}
}
Скорее создайте свойство для члена префикса - таким образом вы можете установить для свойства значение виртуальное / абстрактное
Поля используются для хранения состояния объекта, они помогают объекту инкапсулировать данные и скрывать проблемы реализации от других. Имея возможность переопределить поле, мы передаем проблемы реализации класса клиентскому коду (включая подтипы). Из-за этого большинство языков приняли решение, что нельзя определять переменные экземпляра, которые можно переопределить (хотя они могут быть общедоступными / защищенными ... чтобы вы могли получить к ним доступ).
Вы также не можете помещать переменные экземпляра в интерфейс
Статический или не виртуальный метод или свойство - это просто адрес памяти (для упрощения). Виртуальный метод или свойство идентифицируется записью в таблице. Эта таблица зависит от класса, определяющего метод или свойство. Когда вы переопределяете виртуальный член в производном классе, вы фактически изменяете запись в таблице для производного класса, чтобы указать на метод переопределения. {{ 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 { /* ... */ } }
EDIT: Что касается вашего вопроса об использовании переменной в базовом конструкторе до того, как наследующий класс установит ее, один из способов решить это - определить метод инициализации в базовом классе, переопределить его в наследующем классе и вызвать его из базового конструктора до доступа к любым полям:
// 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";
}
}
EDIT 2: Вы также спросили, в чем разница между virtual/override и name hiding (новое ключевое слово в методах), следует ли их избегать, и могут ли они быть полезны.
Сокрытие имен - это функция, которая нарушает наследование в случае сокрытия виртуальных методов. То есть, если скрыть метод Initialize()
в дочернем классе, то базовый класс его не увидит и не вызовет. Также, если метод Initialize()
был публичным, внешний код, вызывающий Initialize()
по ссылке базового типа, будет вызывать Initialize()
базового типа.
Сокрытие имен полезно, когда метод в базовом классе невиртуальный, а дочерний класс хочет предоставить другую реализацию. Обратите внимание, однако, что это НЕ то же самое, что virtual/override. Ссылки на базовый тип будут вызывать реализацию базового типа, а ссылки на дочерний тип будут вызывать реализацию дочернего типа.
Вы не можете, потому что в этом нет смысла. Чего бы вы достигли, переопределив поле?
В вашем примере, если вы не переопределяете «Show» в классе SecondaryTransaction, то вызов Show в экземпляре SecondaryTransaction фактически будет вызывать метод в базовом классе (Transaction), который, следовательно, будет использовать «Show» в базовом классе, что приведет к выводу:
Первичный: имя1, состояние1 Первичный: имя1, состояние1
Итак, в зависимости от того, какой метод вы вызывали (т. е. один из базовый класс или дочерний класс), код будет иметь другое значение для «префикса», что было бы кошмаром ремонтопригодности . Я подозреваю, что вы, вероятно, хотите / должны сделать, это выставить свойство в транзакции, которое обертывает «префикс».
Вы не можете переопределить поле, потому что это деталь реализации базового класса. Вы можете изменить значение защищенного поля, но, переопределив его, вы, по сути, скажете, что я хочу заменить поле , а не значение .
Что бы я сделал (если бы я абсолютно не хотел / не мог использовать свойства):
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);
}
}
Почему вы хотите переопределить защищенную переменную, разумеется, все, что вы хотите сделать, это установить для нее что-то еще в переопределяющем классе (возможно, в конструкторе) ?
Переопределение поля - это нонсенс. Пометив поле как защищенное, вы автоматически получаете доступ к нему в производных классах. Вы можете переопределять функции, свойства, потому что они используют функции внутри класса.