<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. C#
  3. Object-oriented programming
All lessons in this course

Lesson 23 of 60 15 minIntermediate

Polymorphism: virtual and override

Let each class respond to the same method call in its own way: virtual, override, base calls, method hiding and why it matters.

Polymorphism (Greek for “many forms”) means one call, many behaviours. You call member.Check() without knowing whether member is a Beam, a Column or a Brace, and each object runs its own check. It is what makes inheritance truly useful.

virtual and override

Check.cs
public class StructuralMember{    public string Mark { get; init; } = "";     // virtual: "child classes may replace this"    public virtual string Check() => $"{Mark}: generic check";} public class Beam : StructuralMember{    public override string Check() => $"{Mark}: bending and deflection";} public class Column : StructuralMember{    public override string Check() => $"{Mark}: axial load and buckling";} StructuralMember[] frame ={    new Beam { Mark = "B1" },    new Column { Mark = "C1" },    new StructuralMember { Mark = "X1" }}; foreach (StructuralMember m in frame)    Console.WriteLine(m.Check());      // each object picks its own version
Output
B1: bending and deflection
C1: axial load and buckling
X1: generic check

The loop only knows about StructuralMember. At run time C# looks at the actual object and calls the most specific override. Add a Brace class next year and this loop works without any change.

Interactive

Which method actually runs?

The loop calls m.Check() through a StructuralMember variable. Change how the child classes declare Check().

Members.cs
class StructuralMember{    public virtual void Check() => Console.WriteLine("Member: generic check");} class Beam : StructuralMember{    public override void Check() => Console.WriteLine("Beam: check bending");} class Column : StructuralMember{    public override void Check() => Console.WriteLine("Column: check buckling");} class Brace : StructuralMember{    public override void Check() => Console.WriteLine("Brace: check tension");}
foreach (StructuralMember m in members) m.Check();
[0] Beam objectBeam: check bending
[1] Column objectColumn: check buckling
[2] Brace objectBrace: check tension
Same object, different variable type
Beam b = new Beam(); b.Check()Beam: check bending
StructuralMember m = b; m.Check()Beam: check bending
virtual + override: C# looks at the REAL object at run time and runs its own version. One loop handles every member type correctly. This is polymorphism.

Calling the base version

An override can extend the parent’s behaviour instead of replacing it, using base.Method():

Base.cs
public class StructuralMember{    public virtual string Describe() => $"Mark {Mark}, {Material}";    public string Mark { get; init; } = "";    public string Material { get; init; } = "S355";} public class Beam : StructuralMember{    public string Profile { get; init; } = "IPE200";    public override string Describe() => base.Describe() + $", profile {Profile}";} Console.WriteLine(new Beam { Mark = "B4" }.Describe());
Output
Mark B4, S355, profile IPE200

Overriding ToString, Equals and friends

Every class inherits virtual methods from object. Overriding ToString() is the most common one: it controls how your object looks when printed or seen in the debugger.

ToString.cs
public class Beam{    public string Mark { get; init; } = "";    public double Length { get; init; }     public override string ToString() => $"Beam {Mark} ({Length} m)";} Console.WriteLine(new Beam { Mark = "B9", Length = 7.2 });   // WriteLine calls ToString()
Output
Beam B9 (7.2 m)

Hiding with new: usually a mistake

If a child declares a method with the same name without override, it hides the parent’s method. Which one runs then depends on the variable’s type, not the object’s, as the diagram showed. The compiler warns you (CS0108); adding new only silences the warning. Prefer virtual/override.

sealed override

Sealed.cs
public class Beam : StructuralMember{    public sealed override string Check() => "...";   // subclasses of Beam can't override again}

Overloading vs overriding

OverloadingOverriding
WhatSame name, different parametersSame signature, new implementation in a child class
WhereUsually in the same classBase class (virtual) + derived class (override)
DecidedAt compile time, from the argument typesAt run time, from the object’s real type
ExampleAdd(int, int) and Add(double, double)Beam.Check() replacing StructuralMember.Check()
Quick check

StructuralMember m = new Beam(); m.Check(); with Check virtual in StructuralMember and overridden in Beam. Which runs?

Quick check

Which keyword must the base method have so a child can override it?

Practice

Create a base class Employee with Name and a virtual method decimal MonthlyPay() returning 30000. Derive Manager (pay = base pay + 15000 bonus, using base.MonthlyPay()) and Intern (pay = 12000). Put one of each in an Employee[] and print each name with its pay.

Show the solution
Solution.cs
public class Employee{    public string Name { get; init; } = "";    public virtual decimal MonthlyPay() => 30000m;} public class Manager : Employee{    public override decimal MonthlyPay() => base.MonthlyPay() + 15000m;} public class Intern : Employee{    public override decimal MonthlyPay() => 12000m;} Employee[] staff ={    new Employee { Name = "Asha" },    new Manager { Name = "Ravi" },    new Intern { Name = "Kumar" }};foreach (var e in staff)    Console.WriteLine($"{e.Name}: {e.MonthlyPay()}");
Output
Asha: 30000
Ravi: 45000
Kumar: 12000

Remember

  • Polymorphism: one call, and each object runs its own version.
  • Mark the base method virtual, the child method override.
  • The real object’s type decides which override runs (at run time).
  • base.Method() reuses the parent’s code; avoid hiding with new.
0 of 60 lessons completed