Codectionary / Developer documentation / C#

Polymorphism

Polymorphism lets objects of different derived classes be treated through a common base type or interface, while each object still behaves according to its own specific class at runtime. This is achieved through virtual/override method pairs: a variable declared with a base type can hold any derived class object, and calling an overridden method automatically runs the correct derived version - a mechanism called dynamic dispatch.

Syntax

BaseType variable = new DerivedType();

Examples

Runtime Polymorphism with Method Overriding

The same method call produces different behavior depending on the actual object type.

public class Animal
{
    public virtual void MakeSound() => Console.WriteLine("Some generic sound");
}

public class Dog : Animal
{
    public override void MakeSound() => Console.WriteLine("Woof!");
}

public class Cat : Animal
{
    public override void MakeSound() => Console.WriteLine("Meow!");
}

Animal[] animals = { new Dog(), new Cat(), new Animal() };
foreach (Animal a in animals)
{
    a.MakeSound();  // calls the correct version for each actual object type
}
// Woof!
// Meow!
// Some generic sound

Polymorphism with Interfaces

Treating different implementing classes uniformly through a shared interface type.

public interface IShape
{
    double Area();
}

public class Circle : IShape
{
    private double _radius;
    public Circle(double radius) => _radius = radius;
    public double Area() => Math.PI * _radius * _radius;
}

public class Square : IShape
{
    private double _side;
    public Square(double side) => _side = side;
    public double Area() => _side * _side;
}

IShape[] shapes = { new Circle(3), new Square(4) };
foreach (IShape s in shapes)
{
    Console.WriteLine($"Area: {s.Area():F2}");
}

Polymorphism in Method Parameters

A method that accepts the base type can work with any derived class, without needing to know the specific type.

public class Employee
{
    public string Name;
    public Employee(string name) => Name = name;
    public virtual double CalculatePay() => 0;
}

public class FullTime : Employee
{
    public FullTime(string name) : base(name) { }
    public override double CalculatePay() => 5000;
}

public class Contractor : Employee
{
    public Contractor(string name) : base(name) { }
    public override double CalculatePay() => 3000;
}

void PrintPay(Employee e)  // works with ANY Employee subclass
{
    Console.WriteLine($"{e.Name}: ${e.CalculatePay()}");
}

PrintPay(new FullTime("Fola"));
PrintPay(new Contractor("Zain"));

Best practices

  • Declare variables, parameters, and return types using the base type or interface when you want to write flexible, extensible code
  • Rely on polymorphism instead of type-checking with "is" and manual casting where possible - let overridden methods handle the differences
  • Remember polymorphism applies to virtual/overridden instance methods, not fields or static members, which are resolved based on the declared type instead
  • Design base classes/interfaces around behavior that genuinely varies by subclass, so polymorphism adds real value rather than just ceremony

At a glance

Purpose
Applications on the .NET platform
File extension
.cs
Runs in
.NET runtime
Usually used with
.NET SDK and libraries

Specifications & further reading

Related C# documentation

Classes & Objects
A class is a blueprint for creating objects, bundling related data (fields/properties) and behavior (methods) together. C# is a fully object-oriented language - every application has at least one class, typically with a Main method as its entry point. An object is a specific instance of a class, created with the new keyword, with its own independent copy of the class's instance data.
Constructors
A constructor is a special method that runs automatically when an object is created with new, used to initialize the object's state. A constructor shares its name with the class and has no return type. C# supports constructor overloading (multiple constructors with different parameter lists) and constructor chaining with this(...), where one constructor calls another in the same class to avoid duplicating initialization logic.
Properties (get/set)
Properties are C#'s idiomatic way to expose class data through accessor-like syntax while still allowing controlled access via get and set. Unlike a plain public field, a property can validate a value before it's set, compute a value on the fly, or restrict access to read-only. Auto-implemented properties (using { get; set; } with no body) provide a concise shorthand when no custom logic is needed.
Access Modifiers
Access modifiers control the visibility of classes, fields, methods, and properties from other parts of a program. C# provides public (accessible from anywhere), private (accessible only within the declaring class - the default for class members), protected (accessible within the class and its subclasses), internal (accessible only within the same assembly/project), and combinations like protected internal and private protected.