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.
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
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 versionB1: bending and deflection C1: axial load and buckling X1: generic check
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.
Calling the base version
base.Method():
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());Mark B4, S355, profile IPE200
Overriding ToString, Equals and friends
object. Overriding ToString() is the most common one: it controls how your object looks when printed or seen in the debugger.
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()Beam B9 (7.2 m)
Hiding with new: usually a mistake
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
public class Beam : StructuralMember{ public sealed override string Check() => "..."; // subclasses of Beam can't override again}Overloading vs overriding
| Overloading | Overriding | |
|---|---|---|
Add(int, int) and Add(double, double) | Beam.Check() replacing StructuralMember.Check() |
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
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()}");Asha: 30000 Ravi: 45000 Kumar: 12000
Remember
Polymorphism: one call, and each object runs its own version. Mark the base method virtual, the child methodoverride.The real object’s type decides which override runs (at run time). base.Method()reuses the parent’s code; avoid hiding withnew.