Delegates & Events
Keywordsdelegate, Action, Func, =>, event, +=, -=
NamespaceSystem (for Action/Func)
In UnitySceneManager.sceneLoaded, InputAction.performed, UnityEvent, DOTween's OnComplete

Delegates, Lambdas & Events

A delegate is a variable that holds a method instead of a value, so you can pass "what to do" around and call it later. A lambda (x => ...) is a quick way to write a small method inline. An event is a delegate that other classes can subscribe to but only the owner can fire.

A delegate is a phone number written on a card. You can hand the card to someone, store it, or put several numbers on one card. Later, whoever has the card can "call" it without knowing whose number it is. An event is a mailing list: people add their own number (+=) and remove it (-=), but only the list owner can send the message.

Action and Func

You rarely need to declare your own delegate types. .NET provides two general-purpose ones:

TypeHolds a method that...Example
Actiontakes nothing, returns nothingAction onDone = Respawn;
Action<T1, T2...>takes parameters, returns nothingAction<int> onScore = AddScore;
Func<TResult>takes nothing, returns a valueFunc<bool> isReady = () => loaded;
Func<T1, TResult>takes parameters, returns a value (the last type is the return type)Func<Enemy, float> score = e => e.health;
using System;
using UnityEngine;

public class DelegateDemo : MonoBehaviour
{
    void Start()
    {
        Action greet = SayHello;       // store a method (no parentheses!)
        greet();                       // call it: prints "Hello"

        Func<int, int, int> add = (a, b) => a + b; // lambda
        Debug.Log(add(2, 3));          // 5

        DoLater(2f, () => Debug.Log("Two seconds passed"));
    }

    void SayHello() { Debug.Log("Hello"); }

    // A method that takes "what to do" as a parameter
    void DoLater(float seconds, Action callback)
    {
        StartCoroutine(Wait());
        System.Collections.IEnumerator Wait()
        {
            yield return new WaitForSeconds(seconds);
            callback();
        }
    }
}

Lambdas

x => x * 2                      // one parameter, returns x * 2
(a, b) => a + b                 // two parameters
() => Debug.Log("Hi")           // no parameters
item => { Debug.Log(item); return item.isKey; } // multi-line needs { } and return

A lambda can use variables from around it (it "captures" them):

int threshold = 50;
var strong = enemies.FindAll(e => e.health > threshold); // uses threshold

Events

using System;
using UnityEngine;

public class Health : MonoBehaviour
{
    // Subscribers get the new health value
    public event Action<float> Changed;
    public event Action Died;

    private float hp = 100f;

    public void TakeDamage(float amount)
    {
        hp -= amount;
        Changed?.Invoke(hp);        // ?. : only if someone subscribed
        if (hp <= 0f) Died?.Invoke();
    }
}

public class HealthUI : MonoBehaviour
{
    [SerializeField] private Health health;

    void OnEnable()
    {
        health.Changed += UpdateBar; // subscribe
        health.Died += ShowGameOver;
    }

    void OnDisable()
    {
        health.Changed -= UpdateBar; // ALWAYS unsubscribe
        health.Died -= ShowGameOver;
    }

    private void UpdateBar(float hp)  { /* ... */ }
    private void ShowGameOver()       { /* ... */ }
}

Without the event keyword, any class could do health.Died = null; (wiping everyone's subscriptions) or health.Died() (faking a death). event only allows += and -= from outside.

C# event vs. UnityEvent

Use a C# event for code-to-code connections, and UnityEvent when you want designers to connect things in the Inspector. The full comparison is on the UnityEvent page.

What happens at runtime

  1. A delegate is an object that stores the target object and which method to call on it.
  2. += creates a new delegate containing the old list plus the new method. Invoke() calls them in the order they were added.
  3. A lambda that captures local variables is turned into a hidden class that holds those variables, which means an allocation each time it's created. Avoid creating capturing lambdas every frame.
  4. An event holds a reference to every subscriber. A destroyed object that never unsubscribed stays in the list, and invoking it throws MissingReferenceException (or silently keeps objects alive in memory).

Common Mistakes

See also