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.
At a glance
Section titled “At a glance”Inheritance expresses an is-a relationship; polymorphism lets callers work with a base type while the actual object decides which override executes.
How it works
Section titled “How it works”Base types and derived types
Section titled “Base types and derived types”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.
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()}";}Virtual dispatch and overriding
Section titled “Virtual dispatch and overriding”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.
Character hero = new Mage { Name = "Ari" };Console.WriteLine(hero.Describe()); // Mage: AriWhen to prefer composition
Section titled “When to prefer composition”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.
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”Why overriding works
Section titled “Why overriding works”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.
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.
Is-a versus has-a
Section titled “Is-a versus has-a”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.
Unity has an additional constraint
Section titled “Unity has an additional constraint”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.
When and why
Section titled “When and why”Use virtual only when callers need controlled extension. Prefer composition when types share capabilities rather than identity.
Common mistake
Section titled “Common mistake”Overriding requires matching signatures and an overridable base member. C# classes do not support multiple class inheritance.
Try it yourself
Section titled “Try it yourself”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().
