При наследовании нередко возникает необходимость изменить в классе-наследнике функционал метода, который был унаследован от базового класса. В этом случае класс-наследник может переопределять методы и свойства базового класса.
Те методы и свойства, которые мы хотим сделать доступными для переопределения, в базовом классе помечается модификатором virtual. Такие методы и свойства называют виртуальными.
А чтобы переопределить метод в классе-наследнике, этот метод определяется с модификатором override. Переопределенный метод в классе-наследнике должен иметь тот же набор параметров, что и виртуальный метод в базовом классе.
Например, рассмотрим следующие классы:
class Person { public string Name { get; set; } public Person(string name) { Name = name; } public virtual void Print() { Console.WriteLine(Name); } } class Employee : Person { public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; } }
Здесь класс Person представляет человека. Класс Employee наследуется от Person и представляет сотруднника предприятия. Этот класс кроме унаследованного свойства Name имеет еще одно свойство - Company.
Чтобы сделать метод Print доступным для переопределения, этот метод определен с модификатором virtual. Поэтому мы можем переопределить этот метод, но можем и не переопределять. Допустим, нас устраивает реализация метода из базового класса. В этом случае объекты Employee будут использовать реализацию метода Print из класса Person:
Person bob = new Person("Bob"); bob.Print(); // вызов метода Print из класса Person Employee tom = new Employee("Tom", "Microsoft"); tom.Print(); // вызов метода Print из класса Person
Консольный вывод:
Bob Tom
Но также можем переопределить виртуальный метод. Для этого в классе-наследнике определяется метод с модификатором override, который имеет то же самое имя и набор параметров:
class Employee : Person { public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; } public override void Print() { Console.WriteLine($"{Name} работает в {Company}"); } }
Возьмем те же самые объекты:
Person bob = new Person("Bob"); bob.Print(); // вызов метода Print из класса Person Employee tom = new Employee("Tom", "Microsoft"); tom.Print(); // вызов метода Print из класса Employee
Консольный вывод:
Bob Tom работает в Microsoft
Виртуальные методы базового класса определяют интерфейс всей иерархии, то есть в любом производном классе, который не является прямым наследником от базового класса, можно переопределить виртуальные методы. Например, мы можем определить класс Manager, который будет производным от Employee, и в нем также переопределить метод Print.
При переопределении виртуальных методов следует учитывать ряд ограничений:
Виртуальный и переопределенный методы должны иметь один и тот же модификатор доступа. То есть если виртуальный метод определен с помощью модификатора public, то и переопредленный метод также должен иметь модификатор public.
Нельзя переопределить или объявить виртуальным статический метод.
Кроме конструкторов, мы можем обратиться с помощью ключевого слова base к другим членам базового класса.
В нашем случае вызов base.Print();
будет обращением к методу Print() в классе Person:
class Employee : Person { public string Company { get; set; } public Employee(string name, string company) :base(name) { Company = company; } public override void Print() { base.Print(); Console.WriteLine($"работает в {Company}"); } }
Также как и методы, можно переопределять свойства:
class Person { int age = 1; public virtual int Age { get => age; set{ if(value > 0 && value < 110) age = value; } } public string Name { get; set; } public Person(string name) { Name = name; } public virtual void Print() => Console.WriteLine(Name); } class Employee : Person { public override int Age { get => base.Age; set { if (value > 17 && value < 110) base.Age = value; } } public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; base.Age = 18; // возраст для работников по умолчанию } }
В данном случае в классе Person определено виртуальное свойство Age, которое устанавливает значение, если оно больше 0 и меньше 110. В классе Employee это свойство переопределено - возраст работника должен быть не меньше 18.
Person bob = new Person("Bob"); Console.WriteLine(bob.Age); // 1 Employee tom = new Employee("Tom", "Microsoft"); Console.WriteLine(tom.Age); // 18 tom.Age = 22; Console.WriteLine(tom.Age); // 22 tom.Age = 12; Console.WriteLine(tom.Age); // 22
Также можно запретить переопределение методов и свойств. В этом случае их надо объявлять с модификатором sealed:
class Employee : Person { public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; } public override sealed void Print() { Console.WriteLine($"{Name} работает в {Company}"); } }
При создании методов с модификатором sealed надо учитывать, что sealed применяется в паре с override, то есть только в переопределяемых методах.
И в этом случае мы не сможем переопределить метод Print в классе, унаследованном от Employee.