Перейти к содержимому
Теория · Наследование и интерфейсы · урок 4 из 10

Сокрытие метода: new

В прошлом уроке производный класс переписывал метод, который базовый класс пометил словом virtual. А что будет, если virtual нет, а в производном классе объявлен метод с тем же именем и теми же параметрами?

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

C#
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";
}

Давайте разберём по строкам:

  1. Car car = new Car(); создаёт один объект Car.
  2. Vehicle v = car; даёт тому же объекту второе имя — переменную v типа Vehicle.
  3. car.Who() ищет метод в типе Car и находит там его собственную версию: печатается car.
  4. v.Who() ищет метод в типе Vehicle и находит базовую версию: печатается vehicle.

Один и тот же объект ответил по-разному, потому что версию выбрал тип переменной.

Сокрытие и модификатор new

Метод Who в Car не переопределяет метод Vehicle, а скрывает его. Сокрытие — это объявление метода без override в производном классе, когда у базового класса есть метод с тем же именем и теми же параметрами. Базовый метод при этом никуда не делся: его вызывает любой код, который видит объект через тип Vehicle. А метод с тем же именем, но другими параметрами ничего не скрывает: это перегрузка.

Модификатор new в объявлении public new string Who() сообщает компилятору, что сокрытие задумано. Уберите new, и программа по-прежнему скомпилируется и напечатает то же самое, но с предупреждением:

TEXT
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? Это классический вопрос собеседования. Посмотрим на оба модификатора в одной программе:

C#
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"; }

Давайте разберём три вызова:

  1. truck.Who() печатает truck: Truck переопределил виртуальный метод, и версию выбирает объект.
  2. b.Who() печатает vehicle: Bike скрыл метод, и через переменную типа Vehicle выполняется базовая версия.
  3. bike.Who() печатает bike: у переменной тип Bike, и видна версия из Bike.

Сведём разницу в таблицу:

overridenew
Что нужно в базовом методеvirtualничего
Кто выбирает версиютип объекта, при запускетип переменной, при компиляции
Вызов через переменную базового типаверсия производного классаверсия базового класса

Обычно метод переопределяют, а сокрытие встречается редко. Зато случайное сокрытие — частая ошибка.

Частая ошибка

Переопределяя ToString, забыли написать override:

C#
var bike = new Bike();
Console.WriteLine(bike); // Bike

class Bike
{
    public string ToString() => "my bike"; // ✗
}
TEXT
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.