Polymorphism means "many forms." In programming, it lets different objects respond to the same method call in their own way. You already saw a preview of this with method overriding. Now let us go deeper.
Runtime Polymorphism
This is the most common type. You use a base class reference to point to a derived class object, and C# figures out which method to call at runtime:
class Shape
{
public string Color { get; set; }
public virtual double CalculateArea()
{
return 0;
}
}
class Circle : Shape
{
public double Radius { get; set; }
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double CalculateArea()
{
return Width * Height;
}
}
// Polymorphism in action
Shape[] shapes = new Shape[]
{
new Circle { Radius = 5 },
new Rectangle { Width = 4, Height = 6 },
new Circle { Radius = 3 }
};
foreach (Shape shape in shapes)
{
Console.WriteLine($"Area: {shape.CalculateArea():F2}");
}
// Area: 78.54
// Area: 24.00
// Area: 28.27
Even though the array type is Shape, each object calls its own version of
CalculateArea(). C# decides at runtime which method to invoke based on the
actual object type.
The abstract Keyword
Sometimes you want to force derived classes to implement a method. You can make the
base method abstract:
abstract class Shape
{
public string Color { get; set; }
// No implementation โ derived classes MUST override this
public abstract double CalculateArea();
}
class Circle : Shape
{
public double Radius { get; set; }
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
An abstract class cannot be instantiated directly. You cannot do new Shape().
It exists only to be inherited from. This is useful when you want to define a contract
that all derived classes must follow.
The is and as Keywords
When working with polymorphism, you often need to check or convert types:
Shape shape = new Circle { Radius = 5 };
// Check the type
if (shape is Circle)
{
Console.WriteLine("It is a circle!");
}
// Safe casting with pattern matching
if (shape is Circle c)
{
Console.WriteLine($"Radius: {c.Radius}");
}
// Using as (returns null if conversion fails)
Circle? maybeCircle = shape as Circle;
if (maybeCircle != null)
{
Console.WriteLine($"Radius: {maybeCircle.Radius}");
}