| Keywords | delegate, Action, Func, =>, event, +=, -= |
|---|---|
| Namespace | System (for Action/Func) |
| In Unity | SceneManager.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:
| Type | Holds a method that... | Example |
|---|---|---|
Action | takes nothing, returns nothing | Action onDone = Respawn; |
Action<T1, T2...> | takes parameters, returns nothing | Action<int> onScore = AddScore; |
Func<TResult> | takes nothing, returns a value | Func<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
- A delegate is an object that stores the target object and which method to call on it.
+=creates a new delegate containing the old list plus the new method.Invoke()calls them in the order they were added.- 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.
- 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
- Not unsubscribing. Pair every
+=inOnEnablewith-=inOnDisable. - Unsubscribing a lambda.
Died -= () => Respawn();does nothing, because it's a new lambda. Use a named method, or store the lambda in a field. greet = SayHello();with parentheses calls the method and tries to store its result. Leave off the().- Capturing a loop variable in a
forloop:for (int i...) buttons[i].onClick.AddListener(() => Select(i));makes every button use the final value ofi. Copy it first:int index = i;and captureindex. (foreachvariables are safe.)
See also
- UnityEvent
- Tutorial: UnityEvents in a Game
- LINQ (lambdas everywhere)
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Microsoft C# documentation.