В прошлой теме было рассмотрено определение и переопределение виртуальных методов. Другим способом изменить функциональность метода, унаследованного от базового класса, является скрытие (shadowing / hiding).
Фактически скрытие метода/свойства представляет определение в классе-наследнике метода или свойства, которые соответствует по имени и набору параметров методу или свойству базового класса. Для скрытия членов класса применяется ключевое слово new. Например:
class Person { public string Name { get; set; } public Person(string name) { Name = name; } public void Print() { Console.WriteLine($"Name: {Name}"); } } class Employee : Person { public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; } public new void Print() { Console.WriteLine($"Name: {Name} Company: {Company}"); } }
Здесь определен класс Person, представляющий человека, и класс Employee, представляющий работника предприятия. Employee наследует от Person все свойства и методы. Но в классе Employee кроме унаследованных свойств есть также и собственное свойство Company, которое хранит название компании. И мы хотели бы в методе Print выводить информацию о компании вместе с именем на консоль. Для этого определяется метод Print с ключевым словом new, который скрывает реализацию данного метода из базового класса.
В каких ситуациях можно использовать скрытие? Например, в примере выше метод Print в базовом классе не является виртуальным, мы не можем его переопределить, но, допустим, нас не устраивает его реализация для производного класса, поэтому мы можем воспользоваться сокрытием, чтобы определить нужный нам функционал.
Используем эти классы в программе в методе Main:
Person bob = new Person("Bob"); bob.Print(); // Name: Bob Employee tom = new Employee("Tom", "Microsoft"); tom.Print(); // Name: Tom Company: Microsoft
Консольный вывод программы:
Name: Bob Name: Tom Company: Microsoft
При этом если мы хотим обратиться именно к реализации свойства или метода в базовом классе, то опять же мы можем использовать ключевое слово base и через него обращаться к функциональности базового класса.
class Employee : Person { public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; } public new void Print() { base.Print(); // вызываем метод Print из базового класса Person Console.WriteLine($"Company: {Company}"); } }
Подобным обазом мы можем организовать скрытие свойств:
Person bob = new Person("Bob"); Console.WriteLine(bob.Name); // Bob Employee tom = new Employee("Tom", "Microsoft"); Console.WriteLine(tom.Name); // Mr./Ms. Tom class Person { public string Name { get; set; } public Person(string name) { Name = name; } } class Employee : Person { // скрываем свойство Name базового класса public new string Name { get => $"Mr./Ms. {base.Name}"; set => base.Name = value; } public string Company { get; set; } public Employee(string name, string company) : base(name) { Company = company; } }
В данном случае в классе Employee переопределено свойство Name. В блоке get
нем мы берем значение свойства из базовового класса Person и присоединяем к нему "Mr./Ms.". В блоке set
передаем полученное значение в реализацию свойства Name базового класса Person
В отличие от переопределения C# позволяет применять скрытие к переменным (как к статическим, так и нестатическим) и константам, также используя ключевое слово new:
Console.WriteLine(Person.minAge); // 1 Console.WriteLine(Person.typeName); // Person Console.WriteLine(Employee.minAge); // 18 Console.WriteLine(Employee.typeName); // Employee class Person { public readonly static int minAge = 1; public const string typeName = "Person"; } class Employee : Person { // скрываем поля и константы базового класса public new readonly static int minAge = 18; public new const string typeName = "Employee"; }