Сокрытие метода: new
В прошлом уроке производный класс переписывал метод, который базовый класс пометил словом virtual. А что будет, если virtual нет, а в производном классе объявлен метод с тем же именем и теми же параметрами?
Представьте автопарк, где на общей доске висит правило «как назвать себя», и переписывать его нельзя. Водители легковых кладут в свою папку листовку с тем же заголовком. Кто ищет правило на общей доске, прочтёт общее. Кто открывает папку легковых, прочтёт листовку. Что прочитано, зависит от того, где искали.
Car car = new Car();
Vehicle v = car;
Console.WriteLine(car.Who()); // car
Console.WriteLine(v.Who()); // vehicle
class Vehicle
{
public string Who() => "vehicle";
}
class Car : Vehicle
{
public new string Who() => "car";
}Давайте разберём по строкам:
Car car = new Car();создаёт один объектCar.Vehicle v = car;даёт тому же объекту второе имя — переменнуюvтипаVehicle.car.Who()ищет метод в типеCarи находит там его собственную версию: печатаетсяcar.v.Who()ищет метод в типеVehicleи находит базовую версию: печатаетсяvehicle.
Один и тот же объект ответил по-разному, потому что версию выбрал тип переменной.
Сокрытие и модификатор new
Метод Who в Car не переопределяет метод Vehicle, а скрывает его. Сокрытие — это объявление метода без override в производном классе, когда у базового класса есть метод с тем же именем и теми же параметрами. Базовый метод при этом никуда не делся: его вызывает любой код, который видит объект через тип Vehicle. А метод с тем же именем, но другими параметрами ничего не скрывает: это перегрузка.
Модификатор new в объявлении public new string Who() сообщает компилятору, что сокрытие задумано. Уберите new, и программа по-прежнему скомпилируется и напечатает то же самое, но с предупреждением:
warning CS0108: 'Car.Who()' hides inherited member 'Vehicle.Who()'. Use the new keyword if hiding was intended.Компилятор подозревает, что одинаковое имя появилось случайно. new снимает это подозрение и больше ничего не меняет. Не путайте этот модификатор с new в выражении new Car(): слово то же, а смысл другой.
Здесь аналогия с доской неточна. В автопарке читатель сам решает, куда смотреть. В программе выбирает компилятор ещё до запуска, и смотрит он только на тип переменной.
override или new
Чем override отличается от new? Это классический вопрос собеседования. Посмотрим на оба модификатора в одной программе:
Vehicle truck = new Truck();
Bike bike = new Bike();
Vehicle b = bike;
Console.WriteLine(truck.Who()); // truck
Console.WriteLine(b.Who()); // vehicle
Console.WriteLine(bike.Who()); // bike
class Vehicle
{
public virtual string Who() => "vehicle";
}
class Truck : Vehicle { public override string Who() => "truck"; }
class Bike : Vehicle { public new string Who() => "bike"; }Давайте разберём три вызова:
truck.Who()печатаетtruck:Truckпереопределил виртуальный метод, и версию выбирает объект.b.Who()печатаетvehicle:Bikeскрыл метод, и через переменную типаVehicleвыполняется базовая версия.bike.Who()печатаетbike: у переменной типBike, и видна версия изBike.
Сведём разницу в таблицу:
override | new | |
|---|---|---|
| Что нужно в базовом методе | virtual | ничего |
| Кто выбирает версию | тип объекта, при запуске | тип переменной, при компиляции |
| Вызов через переменную базового типа | версия производного класса | версия базового класса |
Обычно метод переопределяют, а сокрытие встречается редко. Зато случайное сокрытие — частая ошибка.
Частая ошибка
Переопределяя ToString, забыли написать override:
var bike = new Bike();
Console.WriteLine(bike); // Bike
class Bike
{
public string ToString() => "my bike"; // ✗
}warning CS0114: 'Bike.ToString()' hides inherited member 'object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.Программа компилируется и печатает Bike, а не my bike. Без override метод ToString в Bike скрыл виртуальный ToString из object вместо того, чтобы переопределить его. Console.WriteLine принимает объект как object и поэтому вызывает версию object, а она возвращает имя типа. Напишите public override string ToString(), и программа напечатает my bike.
Главное
- Метод с тем же именем и параметрами без
overrideскрывает метод базового класса; модификаторnewговорит, что так задумано, и убирает предупреждениеCS0108. - При сокрытии версию выбирает тип переменной, а при переопределении — тип объекта.
- Через переменную базового типа метод, объявленный с
new, не виден: выполняется версия базового класса. - Забытый
overrideпревращает переопределение в сокрытие, и компилятор предупреждаетCS0114.