Summary

An interface is a list of method (and property) signatures with no implementation. Any class that implements an interface promises to provide concrete implementations of everything on that list. Where csharp-inheritance says “I can do these things because my parent can,” an interface says “I can do these things because I have guaranteed it.” Interfaces enable programming to an abstraction — code that depends on an interface works with any class implementing it, without caring about internal details.

Unity itself uses interfaces extensively: IPointerClickHandler, IBeginDragHandler, IDamageable are common patterns. A class can implement as many interfaces as needed but can only have one parent class.


Key ideas

Declaring an interface:

By convention, interface names start with a capital I.

public interface IDamageable
{
    void TakeDamage(int amount);
    int GetHealth();
}

No access modifiers on members, no method bodies — just signatures. Every implementing class must provide these methods.

Implementing an interface:

public class Player : MonoBehaviour, IDamageable
{
    private int health = 100;
 
    public void TakeDamage(int amount)
    {
        health -= amount;
        if (health <= 0) Die();
    }
 
    public int GetHealth() => health;
 
    private void Die() { /* ... */ }
}
 
public class DestructibleCrate : MonoBehaviour, IDamageable
{
    private int health = 30;
 
    public void TakeDamage(int amount)
    {
        health -= amount;
        if (health <= 0) Destroy(gameObject);
    }
 
    public int GetHealth() => health;
}

Player and DestructibleCrate are completely unrelated classes (different parents), but both implement IDamageable. Anything that needs to deal damage does not need to know which it is dealing with:

// Works for any IDamageable — Player, Crate, Boss, etc.
void DealDamage(IDamageable target, int amount)
{
    target.TakeDamage(amount);
}

Multiple interfaces:

public interface IInteractable
{
    void Interact();
    string GetPrompt();
}
 
public class Chest : MonoBehaviour, IDamageable, IInteractable
{
    // Must implement TakeDamage + GetHealth + Interact + GetPrompt
    public void TakeDamage(int amount) { /* ... */ }
    public int GetHealth() => health;
 
    public void Interact()    { Open(); }
    public string GetPrompt() => "Open chest";
 
    private int health = 50;
    private void Open() { /* ... */ }
}

This allows Chest to be treated as IDamageable by combat code and as IInteractable by the player interaction system — two completely different contexts.

Interface variables:

A variable of an interface type can hold any object that implements that interface.

IDamageable target = FindObjectOfType<Player>();  // or any IDamageable
target.TakeDamage(10);

Properties in interfaces:

Interfaces can declare properties as well as methods.

public interface IHasName
{
    string Name { get; }
}

Interface vs abstract class

InterfaceAbstract class
Method implementationsNone (pre-C# 8)Can include fully implemented methods
Multiple inheritanceA class can implement manyA class can only extend one
Fields/stateNot allowedAllowed
When to useDefine a capability contract across unrelated classesDefine a shared base type with some shared logic

Use an interface when: disparate, unrelated classes need to share a behaviour contract (e.g., anything damageable, anything interactable).

Use an abstract class when: related classes share a significant amount of implementation that would be wasteful to duplicate (e.g., all enemies share combat logic).

These are not mutually exclusive — a class can extend an abstract base class and implement interfaces.


In practice (Unity)

Unity’s built-in interfaces:

Unity’s EventSystem uses interfaces for UI callbacks. Any MonoBehaviour that implements them receives the corresponding events:

using UnityEngine.EventSystems;
 
public class ClickableButton : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler
{
    public void OnPointerClick(PointerEventData data)
    {
        Debug.Log("Clicked!");
    }
 
    public void OnPointerEnter(PointerEventData data)
    {
        Debug.Log("Hovered!");
    }
}

No subclassing required — attaching this component is enough. Unity’s EventSystem reflects on the component and calls the interface methods automatically.

Finding and using damageable targets:

// Get the IDamageable from whatever object was hit
void OnCollisionEnter2D(Collision2D col)
{
    IDamageable target = col.gameObject.GetComponent<IDamageable>();
    if (target != null)
    {
        target.TakeDamage(damage);
    }
}

GetComponent<IDamageable>() works with any component implementing the interface — no need to check for Player, Enemy, Crate separately.


Evidence

From Miles (2019, §4.8): “Interface: ‘I can do these things because I have told you I can.’ Inheritance: ‘I can do these things because my parent can’.”

From Miles (2019, §4.8.7): “Interfaces are a way that you can bring together a set of behaviours. They are packaged as a list of methods which a class must contain if it implements the interfaces.”


Gotchas

  • If a class implements an interface but does not implement every member, the compiler will reject it with an error.
  • Interface methods are implicitly public — do not add an access modifier when implementing them; doing so causes a compile error.
  • Checking if an object implements an interface: use is or as. obj as IDamageable returns null if the cast fails; obj is IDamageable d both tests and assigns.
  • In older Unity versions (pre-2019.4), GetComponent<IInterface>() had limited support — ensure your Unity version handles it correctly. Modern Unity handles it fine.