Абстрактные классы
Попробуйте купить «транспорт вообще». В салоне вам предложат легковую машину, грузовик или велосипед, но не транспорт без вида. «Транспорт» — общее понятие: оно описывает то, что есть у всех видов, а само по себе в автопарке не стоит.
У любого транспорта есть, например, наибольшая скорость. Но назвать её для «транспорта вообще» нельзя: у легковой она одна, у велосипеда другая. Обычный класс Vehicle заставил бы вписать в метод MaxSpeed какое-то число наугад. Нужен способ сказать: «Этот метод есть у всех, а тело пусть напишет каждый вид».
Vehicle car = new Car("Lada");
Console.WriteLine(car.Describe()); // Lada: 180 km/h
abstract class Vehicle
{
public string Name;
protected Vehicle(string name) { Name = name; }
public abstract int MaxSpeed();
public string Describe() => $"{Name}: {MaxSpeed()} km/h";
}
class Car : Vehicle
{
public Car(string name) : base(name) { }
public override int MaxSpeed() => 180;
}Давайте разберём по строкам:
abstract class Vehicleобъявляет класс, объект которого создать нельзя: он нужен только как базовый.public abstract int MaxSpeed();— метод без тела: вместо фигурных скобок стоит точка с запятой.Describe— обычный метод с телом. Он вызываетMaxSpeed, не зная, каким окажется вид транспорта.CarпереопределяетMaxSpeedчерезoverrideи возвращает180.car.Describe()печатаетLada: 180 km/h: метод изVehicleвызвал версиюMaxSpeedизCar.
Абстрактный класс
Абстрактный класс — класс с модификатором abstract, объект которого создать нельзя. Строка var v = new Vehicle("Cart"); не скомпилируется:
error CS0144: Cannot create an instance of the abstract type or interface 'Vehicle'Зато переменная абстрактного типа разрешена: Vehicle car = new Car("Lada"); ведёт к объекту Car.
В остальном абстрактный класс устроен как обычный: в нём бывают поля, конструкторы и методы с телом. Конструктор Vehicle объявлен protected: вызывают его только конструкторы производных классов через : base(name), а больше и некому.
Абстрактный метод
Абстрактный метод — метод с модификатором abstract и без тела. Он объявляет: «Такой метод есть у каждого наследника», а тело оставляет наследникам. Для него действуют три правила:
- абстрактный метод бывает только в абстрактном классе;
- он виртуальный сам по себе: его переопределяют через
override, хотя словаvirtualу него нет; - неабстрактный производный класс обязан переопределить его, если этого ещё не сделал класс выше по цепочке наследования.
Второй вид транспорта — ещё один класс со своей версией MaxSpeed:
Vehicle bike = new Bike("BMX");
Console.WriteLine(bike.Describe()); // BMX: 25 km/h
class Bike : Vehicle
{
public Bike(string name) : base(name) { }
public override int MaxSpeed() => 25;
}Этот фрагмент продолжает первую программу: инструкции добавьте после её инструкций, а класс Bike — в конец файла. Метод Describe написан один раз, а работает для каждого вида со своей скоростью.
Так абстрактный класс делит работу: общее делает сам, а то, что у видов различается, поручает наследникам. Такой подход называют абстракцией: код работает с общим понятием «транспорт» и не зависит от подробностей каждого вида.
Здесь аналогия неточна. «Транспорта вообще» в автопарке не встретить, а переменная типа Vehicle в программе есть, и ведёт она всегда к объекту конкретного вида.
abstract или virtual
На собеседовании часто просят сравнить абстрактный метод с виртуальным. Оба можно переопределить через override, а различаются они так:
- у
virtual-метода есть тело, и производный класс может его переопределить, а может оставить как есть; - у
abstract-метода тела нет, и неабстрактный производный класс не скомпилируется, пока метод не переопределён.
Частая ошибка
Объявили новый вид транспорта и забыли переопределить абстрактный метод:
class Truck : Vehicle // ✗
{
public Truck(string name) : base(name) { }
}error CS0534: 'Truck' does not implement inherited abstract member 'Vehicle.MaxSpeed()'Программа не компилируется. У объекта Truck не было бы версии MaxSpeed, и вызову MaxSpeed() из Describe нечего было бы выполнить. Поэтому компилятор требует тело. Добавьте в Truck строку public override int MaxSpeed() => 90;.
Главное
- Объект абстрактного класса создать нельзя (
CS0144), но переменная его типа может вести к объекту производного класса. - У абстрактного метода нет тела; неабстрактный наследник не скомпилируется, пока метод не переопределён через
override. - В абстрактном классе бывают поля, конструкторы и обычные методы с телом.
virtual-метод переопределить можно, аabstract-метод — нужно.