Skip to content

Inheritance and polymorphism

↑

Examples are independent C# snippets for modern .NET console projects unless noted otherwise. Class declarations and top-level code can live in Program.cs when top-level statements come first.

Inheritance expresses an is-a relationship; polymorphism lets callers work with a base type while the actual object decides which override executes.

A class can inherit from one base class in C#. Derived classes receive accessible base members. Use inheritance for genuine substitutability rather than just sharing a few lines of code.

Example.cs
public class Character
{
public string Name { get; init; } = "Unknown";
public virtual string Describe() => Name;
}
public class Mage : Character
{
public override string Describe() => $"Mage: {base.Describe()}";
}

Mark a base method virtual (or abstract) and override it in derived classes. When calling through a base reference, the runtime chooses the override of the actual object. new member hiding is different from overriding.

This snippet continues the Character and Mage definitions above.

Example.cs
Character hero = new Mage { Name = "Ari" };
Console.WriteLine(hero.Describe()); // Mage: Ari

Inheritance couples classes to a shared hierarchy. If a behavior is interchangeable, prefer composition: a class holds a separate object implementing an interface. It is often easier to test and evolve.

Example.cs
public interface IMovement { void Move(); }
public class PlayerController
{
private readonly IMovement movement;
public PlayerController(IMovement movement) => this.movement = movement;
public void Tick() => movement.Move();
}

Deep dive: behavior depends on the actual object

Section titled “Deep dive: behavior depends on the actual object”

A variable can be declared as a base type while referencing a derived object. When a method is declared virtual in the base class and override in the subclass, the runtime dispatches the call to the most specific override for that object. The variable’s declared type controls which members can be requested; the object’s runtime type determines which overridden implementation runs.

Program.cs
Enemy enemy = new FlyingEnemy();
Console.WriteLine(enemy.Move()); // "Flying"
public class Enemy
{
public virtual string Move() => "Walking";
}
public sealed class FlyingEnemy : Enemy
{
public override string Move() => "Flying";
}

Without virtual/override, a method declared with new hides a base member instead of overriding it. Which member is called may then depend on the expression’s compile-time type, which is often surprising. Use override when you intend polymorphic behavior, and use base.Move() if the subclass needs to extend rather than completely replace the base implementation.

Inheritance makes sense when the derived class really is a specialized version of the base type and preserves its promises. Composition means an object has a collaborator, for example a Player with a HealthComponent or MovementStrategy. Prefer composition when behaviors vary independently or need to be swapped at runtime. A very deep class hierarchy often couples unrelated responsibilities.

Your gameplay scripts already inherit from MonoBehaviour, so C# only lets them inherit from that one class hierarchy. Interfaces such as IDamageable let several unrelated component types advertise a common capability without forcing them into another common base class. Do not expect methods on a derived MonoBehaviour to run automatically unless Unity invokes the relevant lifecycle callback or your code calls them.

Use virtual only when callers need controlled extension. Prefer composition when types share capabilities rather than identity.

Overriding requires matching signatures and an overridable base member. C# classes do not support multiple class inheritance.

Create a base Animal class and override Speak in a Dog class; call through an Animal reference.

Reveal a solution

Declare public virtual string Speak() and public override string Speak().