Inheritance, Polymorphism and Abstraction
A class can derive from one base class and inherits its members: class Car : Vehicle { }.
Concept
A class can derive from one base class and inherits its members: class Car : Vehicle { }. The derived class can add members, override virtual members and call base members with base.. Constructors are not inherited; a derived constructor calls a base constructor with : base(...) (or implicitly the parameterless one). sealed prevents further derivation. All classes ultimately derive from object.
"Many forms": code written against Vehicle works with any derived type, and the runtime type decides which implementation runs.
Base member that derived classes may override Replaces a virtual/abstract member; called through a base reference too
Member without implementation; derived non-abstract classes must override sealed override
Overrides and prevents further overriding new (modifier) Hides a base member; which one runs depends on the *static* type of the variable
In the example, calling Category() through a Vehicle variable runs the base version even though the object is an ElectricCar, because Category is not virtual and is only hidden.
An abstract class cannot be instantiated; it defines a common base with some implemented members and some abstract ones. Interfaces (Module 16) are the other abstraction tool. Choose an abstract class when derived types share state or implementation; choose an interface for a capability that unrelated types can have.
Type Tests and Casts is tests a type (and can declare a variable: if (v is Car car)); as returns null instead of throwing; a cast (Car)v throws InvalidCastException if the object is not a Car.
classDiagram
class Vehicle {
+Name
+Wheels
+Start()
+Describe()
}
class Car {
+Doors
}
class Truck {
+PayloadTons
+Start()
}
class ElectricCar {
+BatteryKwh
+Start()
}
Vehicle <|-- Car
Vehicle <|-- Truck
Car <|-- ElectricCarWhy It Matters
Polymorphism lets you add new kinds of objects (a new vehicle type, a new ECU driver) without changing the code that uses them — the basis of plugin systems, test frameworks and UI toolkits.
Syntax
public abstract class Base { public abstract void A(); public virtual void B() { } }
public class Derived : Base
{
public Derived() : base() { }
public override void A() { }
public override void B() { base.B(); }
}Example
// Inheritance, polymorphism, abstraction
List<Vehicle> fleet =
[
new Car("Aurora", doors: 4),
new Truck("Hauler", payloadTons: 18),
new ElectricCar("Volt-X", doors: 2, batteryKwh: 75),
];
foreach (Vehicle v in fleet)
{
v.Start(); // virtual call: runtime type decides
Console.WriteLine($" {v.Describe()} | wheels: {v.Wheels} | service every {v.ServiceIntervalKm} km");
}
// Method hiding (new) vs overriding (override)
Vehicle asBase = new ElectricCar("Spark", 2, 40);
ElectricCar asDerived = (ElectricCar)asBase;
Console.WriteLine($"hidden member via base: {asBase.Category()}, via derived: {asDerived.Category()}");
// Type checks and casts
foreach (Vehicle v in fleet)
{
if (v is Car car) Console.WriteLine($"{v.Name} is a Car with {car.Doors} doors");
if (v as Truck is { } t) Console.WriteLine($"{t.Name} carries {t.PayloadTons} t");
}
public abstract class Vehicle(string name)
{
public string Name { get; } = name;
public abstract int Wheels { get; } // must be implemented
public virtual int ServiceIntervalKm => 15_000; // may be overridden
public virtual void Start() => Cons
…Expected Result
Aurora: engine start
Car 'Aurora' | wheels: 4 | service every 15000 km
Hauler: glow plugs preheat
Hauler: engine start
Truck 'Hauler' | wheels: 6 | service every 30000 km
Volt-X: HV contactors closed (75 kWh)
ElectricCar 'Volt-X' | wheels: 4 | service every 40000 km
hidden member via base: vehicle, via derived: EV
Aurora is a Car with 4 doors
Hauler carries 18 t
Volt-X is a Car with 2 doorsCommon Mistakes
Not implementing an abstract member — real compiler error:
Instantiating an abstract class:
Using new (hiding) when you meant override — polymorphism silently stops working.
Deep inheritance hierarchies — prefer composition ("has-a") over inheritance ("is-a") when in doubt.
Violating the Liskov Substitution Principle: a derived class that throws NotSupportedException for base behaviour (Module 55).
Forgetting to call base.Method() when the base implementation does required work.
var v = new Vehicle();
abstract class Vehicle
{
public abstract void Start();
}
class Bike : Vehicle
{
}m15_err.cs(8,7): error CS0534: 'Bike' does not implement inherited abstract member 'Vehicle.Start()'var v = new Vehicle();
abstract class Vehicle
{
public abstract void Start();
}m15_err2.cs(1,9): error CS0144: Cannot create an instance of the abstract type or interface 'Vehicle'Best Practices
Keep hierarchies shallow; seal classes that are not designed for inheritance.
Make members virtual deliberately, and document what overrides must do.
Prefer interfaces plus composition for flexibility; use abstract base classes for shared implementation.
Use pattern matching (is Car car) instead of as + null check.
Interview Relevance
Very high: "four pillars of OOP", "overriding vs overloading vs hiding", "abstract class vs interface", "what does sealed do?", "is multiple inheritance allowed?" (not for classes; yes for interfaces).
Practice Questions
Add a Motorcycle class with 2 wheels and a different Start message.
Make Describe virtual and override it in Truck to include the payload.
Explain why asBase.Category() printed vehicle.
Create an abstract Shape with Area() and implement Circle and Rectangle; sum the areas of a list of shapes.
Quick Quiz
Q1. (Predict the output) Vehicle v = new ElectricCar(...); Console.WriteLine(v.Category()); when ElectricCar declares public new string Category() => "EV";.
Answer: vehicle — hiding uses the static type (verified).
Q2. (Multiple choice) Which keyword prevents a class from being inherited? A) static B) sealed C) abstract D) readonly Answer: B.
Q3. (True/False) A C# class can inherit from two base classes.
Answer: False. Single class inheritance; multiple interfaces are allowed.
Q4. (Find the bug) class Bike : Vehicle { } where Vehicle has public abstract void Start();.
Answer: Bike must override Start or be declared abstract (CS0534, verified).
Coding Challenge
Create an abstract Sensor with an abstract double Read() and a template method string Report() that formats the reading with a unit provided by an abstract property. Implement TemperatureSensor and PressureSensor.
List<Sensor> sensors = [new TemperatureSensor(), new PressureSensor()];
foreach (var s in sensors) Console.WriteLine(s.Report());
abstract class Sensor
{
protected abstract string Unit { get; }
public abstract double Read();
public string Report() => $"{GetType().Name}: {Read():F1} {Unit}"; // template method
}
class TemperatureSensor : Sensor
{
protected override string Unit => "C";
public override double Read() => 41.5;
}
class PressureSensor : Sensor
{
protected override string Unit => "kPa";
public override double Read() => 101.3;
}