Skip to content

Polymorphism

Polymorphism (β€œmany forms”) means code can work with objects of a base type while actually running the correct derived type’s behavior at runtime. In C#, this requires a base class method marked virtual (or abstract) and a derived class method marked override β€” calling the method through a base-typed reference still invokes the derived class’s version.

public class Shape
{
public virtual double GetArea() => 0;
public virtual string Describe() => $"A shape with area {GetArea():F2}";
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double GetArea() => Math.PI * Radius * Radius;
}
public class Square : Shape
{
public double Side { get; set; }
public override double GetArea() => Side * Side;
}
List<Shape> shapes = new()
{
new Circle { Radius = 3 },
new Square { Side = 4 },
};
foreach (var shape in shapes)
{
Console.WriteLine(shape.Describe()); // calls the CORRECT override for each, via the same Shape reference
}

Casting to a base type and expecting the base implementation to run β€” polymorphism means the actual object’s overridden method runs regardless of the reference’s declared type, which surprises developers expecting C-style static dispatch.

Shape shape = new Circle { Radius = 5 };
Console.WriteLine(shape.GetArea()); // Circle's GetArea() runs, NOT Shape's -- this is polymorphism working correctly
// This is often misunderstood as a "bug" by developers new to OOP,
// but it's the entire point: the runtime type determines behavior, not the compile-time reference type
  1. What two keywords are required for polymorphic method dispatch to work between a base and derived class?

    Answervirtual on the base class method, and override on the derived class's version.
  2. If you store a Circle object in a variable typed as Shape, which GetArea() implementation runs when you call it?

    AnswerCircle's overridden implementation β€” the actual runtime type of the object determines which override runs, not the declared type of the variable.
  3. Why is polymorphism useful when working with collections like List<Shape> containing different shape subtypes?

    AnswerIt lets you write one loop that calls the same method on every item, and each item automatically runs its own correct, type-specific behavior β€” no manual type-checking or branching required.