Many things in the real world are specialised versions of something more general. A savings account and a current account are both bank accounts. A car and a bicycle are both vehicles. Inheritance lets one class build on another: the derived class (or subclass) automatically gets the fields, properties and methods of its base class, and adds or changes what it needs.
Inheritance avoids repeating shared code, but its real power is polymorphism, which means "many forms". You can store different kinds of objects in one list of the base type, call the same method on each, and every object responds in its own way. A payroll system calls CalculatePay() on every employee, and salaried staff, hourly staff and contractors each work it out differently.
In this lesson you'll create class hierarchies, call base constructors, override methods with virtual and override, write abstract classes that define a template for subclasses, check types with is, and learn when inheritance is the wrong tool.
Base and derived classes
class Animal
{
public string Name { get; }
public Animal(string name) => Name = name;
public void Eat() => Console.WriteLine($"{Name} is eating");
}
class Dog : Animal
{
public Dog(string name) : base(name) { }
public void Fetch() => Console.WriteLine($"{Name} fetches the ball");
}class Dog : Animalmeans Dog inherits from Animal. A Dog is an Animal.: base(name)calls the base class constructor so it can set up its part of the object.- Dog has
NameandEat()from Animal, plus its ownFetch(). - A class can inherit from only one base class. Every class ultimately inherits from
object, which is whereToString,EqualsandGetHashCodecome from. protectedmembers are visible in derived classes but not to outside code.
virtual, override and polymorphism
A base class marks a method virtual to allow derived classes to replace it with override:
class Shape
{
public virtual double Area() => 0;
}
class Circle : Shape
{
public double Radius { get; init; }
public override double Area() => Math.PI * Radius * Radius;
}
Shape s = new Circle { Radius = 2 };
Console.WriteLine(s.Area()); // 12.566..., the Circle version runsEven though s is declared as Shape, the object's real type decides which version runs. That's polymorphism. Inside an override, base.Area() calls the base version, which is handy for extending behaviour rather than replacing it.
abstract and sealed
An abstract class can't be created with new; it exists only to be inherited. It can contain abstract members with no body, which every non-abstract subclass must override:
abstract class Employee
{
public string Name { get; init; } = "";
public abstract decimal MonthlyPay();
}A sealed class can't be inherited from at all, and sealed override stops further overriding. string is a sealed class.
To check an object's type, use is with a pattern: if (shape is Circle c) Console.WriteLine(c.Radius);. The as operator converts or gives null.
Hiding a method with new instead of overriding it
If a derived class declares a method with the same name as a non-virtual method in its base class, it doesn't override it. It hides it, and the compiler warns you with CS0108. Adding the new keyword silences the warning, but the behaviour is very different from override:
class Animal
{
public void Speak() => Console.WriteLine("Animal sound");
public virtual void Describe() => Console.WriteLine("An animal");
}
class Dog : Animal
{
public new void Speak() => Console.WriteLine("Woof");
public override void Describe() => Console.WriteLine("A dog");
}
Animal a = new Dog();
a.Speak(); // Animal sound: the variable's type decides
a.Describe(); // A dog: the object's type decidesWith hiding, the declared type of the variable chooses which method runs, so the same object behaves differently depending on how you refer to it. That's almost never what you want. When you see CS0108, the fix is usually to make the base method virtual and use override, not to add new.
C# Object-Oriented Programming Syntax
Inheritance syntax
class Base
{
protected int x;
public Base(int x) => this.x = x;
public virtual string Describe() => "base";
}
class Derived : Base
{
public Derived() : base(5) { }
public override string Describe() => base.Describe() + " + derived";
}
abstract class Shape { public abstract double Area(); }
sealed class Square : Shape { public override double Area() => 1; }
if (obj is Derived d) { }C# Object-Oriented Programming Summary
- class B : A makes B inherit A's members; C# allows one base class.
- : base(...) calls the base constructor; protected members are visible to subclasses.
- virtual methods can be replaced with override; the object's real type decides which runs.
- abstract classes can't be instantiated and can force subclasses to implement members.
- sealed prevents inheritance; is and as check and convert types.
C# Object-Oriented Programming Examples
Each example shows the code, the output it produces and what each part does.
A payroll with polymorphism
var staff = new List<Employee>
{
new SalariedEmployee("Ada", 42000m),
new HourlyEmployee("Ben", 15.50m, 120),
new HourlyEmployee("Cy", 18m, 90)
};
decimal total = 0;
foreach (Employee e in staff)
{
decimal pay = e.MonthlyPay();
total += pay;
Console.WriteLine($"{e.Name,-4} {e.Kind,-9} {pay,9:F2}");
}
Console.WriteLine($"Total payroll: {total:F2}");
abstract class Employee(string name)
{
public string Name { get; } = name;
public virtual string Kind => "Staff";
public abstract decimal MonthlyPay();
}
class SalariedEmployee(string name, decimal salary) : Employee(name)
{
public override string Kind => "Salaried";
public override decimal MonthlyPay() => Math.Round(salary / 12, 2);
}
class HourlyEmployee(string name, decimal rate, int hours) : Employee(name)
{
public override string Kind => "Hourly";
public override decimal MonthlyPay() => rate * hours;
}Ada Salaried 3500.00
Ben Hourly 1860.00
Cy Hourly 1620.00
Total payroll: 6980.00Line-by-line explanation
- Line 1One list holds different kinds of employee through their shared base type.
- Line 11Each object runs its own MonthlyPay; the loop doesn't need to know which kind it has.
- Line 17An abstract class can't be created directly.
- Line 21Every non-abstract subclass must provide MonthlyPay.
- Line 24Primary constructor parameters pass name on to the base class.
- Line 2742000 divided by 12 is 3500.
- Line 3315.50 times 120 hours is 1860.
Extending behaviour with base
Notification[] items =
{
new Notification("Your order shipped"),
new UrgentNotification("Payment failed")
};
foreach (var n in items)
{
Console.WriteLine(n.Format());
}
class Notification(string message)
{
public string Message { get; } = message;
public virtual string Format() => $"[info] {Message}";
}
class UrgentNotification(string message) : Notification(message)
{
public override string Format() => base.Format().Replace("info", "URGENT") + " (action needed)";
}[info] Your order shipped
[URGENT] Payment failed (action needed)Line-by-line explanation
- Line 9The same call produces different results for each object.
- Line 15virtual allows subclasses to override Format.
- Line 20base.Format() reuses the base version and then extends it.
Type checks with is
Vehicle[] fleet = { new Car(4), new Bike(), new Car(2) };
int seats = 0;
foreach (Vehicle v in fleet)
{
if (v is Car car)
{
seats += car.Seats;
}
Console.WriteLine($"{v.GetType().Name}: {v.Wheels} wheels");
}
Console.WriteLine($"Total car seats: {seats}");
abstract class Vehicle
{
public abstract int Wheels { get; }
}
class Car(int seats) : Vehicle
{
public int Seats { get; } = seats;
public override int Wheels => 4;
}
class Bike : Vehicle
{
public override int Wheels => 2;
}Car: 4 wheels
Bike: 2 wheels
Car: 4 wheels
Total car seats: 6Line-by-line explanation
- Line 6is checks the real type and, if it matches, gives a typed variable to use.
- Line 8Seats only exists on Car, so it needs the converted variable.
- Line 10GetType().Name shows the object's actual class.
- Line 16Abstract properties work just like abstract methods.
Common C# Object-Oriented Programming Mistakes
Forgetting virtual on the base method
class Shape { public double Area() => 0; }
class Circle : Shape { public override double Area() => 3.14; } // error CS0506: not marked virtualclass Shape { public virtual double Area() => 0; }
class Circle : Shape { public override double Area() => 3.14; }Not calling the base constructor
class Dog : Animal
{
public Dog(string name) { } // error CS7036: Animal has no parameterless constructor
}class Dog : Animal
{
public Dog(string name) : base(name) { }
}Inheriting just to reuse code
class Car : Engine { } // a car isn't an engineclass Car
{
public Engine Engine { get; } = new();
}C# Object-Oriented Programming in Real Projects
Inheritance in .NET
ASP.NET Core controllers inherit from ControllerBase to get helper methods like Ok() and NotFound(). Unity scripts inherit from MonoBehaviour and override methods such as Start and Update. Exceptions form a hierarchy, with your own exceptions inheriting from Exception. Domain models use abstract base classes for shared behaviour, such as an Entity base with an Id.
C# Object-Oriented Programming FAQ
What is the difference between virtual and abstract in C#?
A virtual method has a body that derived classes may override if they want. An abstract method has no body, can only appear in an abstract class, and every non-abstract derived class must override it. Use abstract when there's no sensible default behaviour.
Does C# support multiple inheritance?
Not for classes: a class can have only one base class. It can, however, implement any number of interfaces, such as class Report : Document, IPrintable, IExportable. Combining one base class with several interfaces, or using composition, covers the cases where other languages use multiple inheritance.
How do I call the base class constructor in C#?
Add : base(arguments) after the derived constructor's parameter list, as in public Dog(string name) : base(name) { }. The base constructor runs first, then the body of the derived one. If the base class has no parameterless constructor and you leave this out, the compiler reports an error.
What is the difference between is and as in C#?
is tests whether an object has a type and returns a bool; with a pattern, if (shape is Circle c), it also gives you a typed variable. as tries the conversion and returns null if it fails, so you must check for null afterwards. Modern C# code mostly uses is with a pattern.
What does sealed mean in C#?
A sealed class can't be used as a base class, so nobody can inherit from it; string is sealed, for example. On a method, sealed override stops classes further down the hierarchy from overriding it again. Sealing is useful when a class wasn't designed to be extended safely.
C# Object-Oriented Programming Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the declaration so Cat inherits from Animal.
class Cat Animal
{
}
How many base classes can a C# class inherit from?
What does this print?
A obj = new B();
Console.WriteLine(obj.Name());
class A { public virtual string Name() => "A"; }
class B : A { public override string Name() => "B"; }
What does : base(name) on a constructor do?
What must a non-abstract class do when it inherits an abstract method?
C# Object-Oriented Programming Quiz
5 questions to check what you learned.
Which keyword allows a method to be overridden?
- override
- sealed
- static
- virtual
Can you create an instance of an abstract class with new?
- No
- Yes
- Only in the same file
- Only with a primary constructor
What does sealed on a class mean?
- It can't have methods
- It's read-only
- No class can inherit from it
- It's abstract
What is polymorphism?
- Treating different derived objects through a base type, with each running its own version of a method
- Having many constructors
- Copying objects
- Using many namespaces
Which class does every C# class ultimately inherit from?
- Base
- object
- Class
- Root