GDKB

Method Overriding

Intermediate25 Minutes readUpdated July 21, 20263 related concepts

Method overriding lets a subclass replace the implementation of a method its parent class already defines, keeping the exact same name and signature. Code that calls the method through a parent-typed reference still runs the *subclass's* version if the object is actually a subclass instance — the correct behavior is chosen at runtime based on the object's real type, not the type of the variable referring to it.

On this page

Method Overriding

Summary

Method overriding lets a subclass replace the implementation of a method its parent class already defines, keeping the exact same name and signature. Code that calls the method through a parent-typed reference still runs the subclass's version if the object is actually a subclass instance — the correct behavior is chosen at runtime based on the object's real type, not the type of the variable referring to it.

Why This Matters

A base Enemy class might define a generic Attack() that walks up and swings a weapon, but a RangedEnemy needs to fire a projectile instead, and a BossEnemy needs a multi-phase attack sequence. Without overriding, handling this requires the calling code to check "what kind of enemy is this?" and branch accordingly — if (enemy is RangedEnemy) ... else if (enemy is BossEnemy) ... — which has to be updated every time a new enemy type is added. Overriding lets every enemy type supply its own Attack(), and calling code just calls enemy.Attack() without ever needing to know which concrete type it's dealing with.

Mental Model

Overriding is a job title with the same responsibilities, filled differently by each employee. Every "Cashier" at a store chain has the same job title and the same core responsibility — "process a transaction" — but a specific store might train its cashiers to do that slightly differently (a scan-based register versus a manual one). A customer just says "please check me out," and whichever concrete implementation of "Cashier" is actually standing there handles it in their own way, without the customer needing to know which store's specific training they received.

Core Explanation

  • virtual on the base marks a method as replaceable. A base class method must be declared virtual (or the class contains an abstract method — see [[CON-0029]]) before any subclass is allowed to override it; a non-virtual method cannot be overridden, only hidden (a distinct, usually-unwanted mechanism).
  • override on the subclass replaces the implementation, keeping the same signature. The overriding method must match the base method's name, parameters, and return type exactly — this is what distinguishes it from overloading (see [[CON-0027]]), which deliberately changes the parameters.
  • Resolution happens at runtime, based on the object's actual type. Enemy enemy = new RangedEnemy(); enemy.Attack(); calls RangedEnemy's override, even though the variable's declared type is the base Enemy — this runtime dispatch is the mechanism underlying polymorphism (see [[CON-0030]]).
  • base.MethodName() calls the parent's version from inside an override. An override doesn't have to fully replace the parent's logic — it can run the base behavior first or last and add to it, rather than duplicating what the base already does.

Syntax / Theory

C# (Unity)

text
public class Enemy : MonoBehaviour
{
    public virtual void Attack()
    {
        Debug.Log($"{name} swings a weapon.");
    }
}

public class RangedEnemy : Enemy
{
    public override void Attack()
    {
        Debug.Log($"{name} fires a projectile.");
    }
}

public class BossEnemy : Enemy
{
    public override void Attack()
    {
        base.Attack(); // Runs the base swing first...
        Debug.Log($"{name} follows up with a ground slam.");
    }
}
csharp
Enemy current = enemies[i]; // Declared type is Enemy, whatever the concrete type.
current.Attack();           // Runs the correct override for whichever type it actually is.

C++ (Unreal)

cpp
class AEnemy : public ACharacter
{
public:
    virtual void Attack();
};

class ARangedEnemy : public AEnemy
{
public:
    virtual void Attack() override; // 'override' is optional but strongly recommended.
};

C++ requires virtual on the base declaration exactly as C# requires virtual; the override keyword on the derived declaration is technically optional but should always be used — it makes the compiler verify a matching virtual method actually exists on the base class, catching typos that would otherwise silently create an unrelated new method instead of an override.

Unreal Blueprint

BlueprintNativeEvent (or BlueprintImplementableEvent) on a C++ function exposes an override point directly in the Blueprint editor — a Blueprint-only subclass can override the C++ base's behavior via a "Event [Name]" node, without touching C++ at all.

Practical Example

A notification system defines a base "how to display" behavior that different notification types customize.

text
public class Notification
{
    public virtual string Format() => $"[Notice] {Message}";
    public string Message;
}

public class WarningNotification : Notification
{
    public override string Format() => $"[⚠ Warning] {Message}";
}

public class ErrorNotification : Notification
{
    public override string Format() => $"[✖ Error] {Message.ToUpper()}";
}
csharp
List<Notification> queue = GetPendingNotifications();
foreach (var note in queue)
    Console.WriteLine(note.Format()); // Correct formatting per type, no type-checking here.

The loop never asks "is this a warning or an error?" — each notification knows how to format itself, and the loop just asks for the result.

Common Mistakes

  • Forgetting virtual on the base method, which makes the subclass's override a compile error — a clear signal, but only after the fact; design the base class with overriding in mind from the start if subclasses are expected to customize behavior.
  • Omitting override in C++ (where it's optional). Without it, a typo'd signature silently creates a new, unrelated method instead of overriding the intended one — always include override so the compiler catches the mismatch.
  • Calling base.Method() unnecessarily, duplicating the entire base implementation when the override was meant to fully replace it — only call base when genuinely extending, not replacing, the inherited behavior.
  • Type-checking (if (enemy is RangedEnemy)) instead of relying on overriding. If a base method could be overridden to produce the correct behavior automatically, manually branching on type is exactly the problem overriding exists to eliminate — see [[CON-0030]].

Game Development Example

A UI system's base Panel class defines default open/close animation behavior that specific panels override.

csharp
public class Panel : MonoBehaviour
{
    public virtual void Open() => canvasGroup.alpha = 1f;
    public virtual void Close() => canvasGroup.alpha = 0f;
}

public class InventoryPanel : Panel
{
    public override void Open()
    {
        base.Open();
        RefreshItemList(); // Extends the base behavior instead of replacing it.
    }
}

Every panel in the game can be opened and closed identically from calling code (panel.Open()), while each panel type customizes exactly what "open" additionally means for it.

Best Practices

  • Mark a base method virtual only when subclasses genuinely need to customize it — marking everything virtual by default signals no behavior is settled, which makes the base class's contract unclear.
  • Always use override explicitly (mandatory in C#, optional-but-required by convention in C++) so signature mismatches are caught at compile time.
  • Call base.Method() when extending inherited behavior; omit it entirely when the override is a full replacement.
  • Prefer overriding over type-checking (is/as chains) whenever the differing behavior can be expressed as "each type does its own version of this one operation."

Exercises

  1. Create a base Pickup class with a virtual void OnCollected() that logs a generic message, then create HealthPickup and AmmoPickup subclasses that each override it with type-specific behavior.
  2. Take the Panel example and add a SettingsPanel override of Close() that calls base.Close() and additionally saves settings — explain why calling base.Close() here is the right choice rather than duplicating the fade logic.
  3. You're designing an Ability base class where Cast() behavior varies significantly across 15+ ability types. Decide between one large virtual Cast() override per ability versus composing abilities from smaller, non-overridden building-block methods, and justify the trade-off.
  4. Research why calling a virtual method from inside a base class's own constructor is considered dangerous in both C# and C++, and explain what state the overriding subclass's fields might be in at that point.
  • Inheritance — overriding is only possible because a subclass inherits the base class's method in the first place; it's the mechanism for customizing what's inherited.
  • Method Overloading — easily confused with overriding, but solves the opposite problem: same signature vs. varying signatures, runtime resolution vs. compile-time resolution.
  • Polymorphism — runtime override resolution (calling the right version through a base-typed reference) is the concrete mechanism that makes polymorphism work.

Further Reading

Related Concepts