WeakRef
When subscribing to delegates and events in Unity/C#, publishers hold strong references to subscriber instances, so even after unsubscribing the GC cannot collect them, and "ghost callbacks" plus memory leaks often occur--e.g. callbacks still firing after scene changes or object destruction.
This package wraps delegates, Action, and events in weak references, so when the target is collected by the GC (plain C# objects) or destroyed in Unity (UnityEngine.Object), it is automatically treated as "dead."
What it does
- Reduces memory leaks: If you forget to unsubscribe, references disappear once the target instance is gone, so it becomes eligible for GC.
- Prevents ghost callbacks: No invocations on destroyed Unity objects or already-collected instances.
- Unity-aware:
UnityEngine.Objectis considered "dead" atDestroy()time (viaUnityWeakReference, respecting Unity null semantics).
Provided types: UnityWeakReference, WeakDelegate, WeakAction/WeakAction, WeakEvent/WeakEvent, WeakEventSource. See the Type Summary and sections below for details.
Cautions
- No lambdas or anonymous methods: Weak references track only the delegate target instance. Lambdas and anonymous methods target compiler-generated closures, not the intended "subscriber" instance. Use method groups or named methods only.
- e.g.
Subscribe(handler.OnRaise)/Subscribe(() => handler.OnRaise())
- e.g.
- Not thread-safe:
WeakEvent,WeakEventSource, etc. do not assume concurrent multi-threaded access. Synchronize subscribe/unsubscribe/raise from other threads at the call site. - Unity-only type:
UnityWeakReferencesupports onlyT : UnityEngine.Object. For plain C# objects use .NETWeakReferenceor this package'sWeakDelegate/WeakAction. - Cleanup on Raise: In
WeakEvent/WeakEventSource, "dead" subscribers are removed from the list when Raise is called. If Raise is infrequent, dead references may remain until then.
Installing the WeakRef Package (for Unity 2021.3 or later)
- Open Window > Package Manager
- Click the '+' button > Select "Add package from git URL..."
- Enter the following URL:
https://github.com/xpTURN/WeakRef.git?path=src/WeakRef/Assets/WeakRef
Type Summary
| Type | Purpose |
|---|---|
| UnityWeakReference |
Weak reference for UnityEngine.Object only. Treats destroyed objects as "dead" using Unity null semantics. |
| WeakDelegate |
Holds the delegate target weakly. TryGetDelegate() returns the delegate only when the target is alive. |
| WeakAction / WeakAction |
Holds an Action or Action weakly. Invoke() runs only when the target is alive. |
| WeakEvent / WeakEvent |
Weak event based on Action. Subscribers that are GC'd or destroyed are removed on Raise. |
| WeakEventSource |
Weak event based on EventHandler. Sender/args pattern. |
UnityWeakReference
A weak-reference wrapper for Unity's UnityEngine.Object.
It extends System.WeakReference but respects Unity's special null semantics so that IsAlive and the target are evaluated correctly.
Why it exists
- Plain
WeakReference: Once the target is collected by the GC,Targetbecomes null andIsAliveis false. - Unity objects:
UnityEngine.Objectcan be in a "destroyed" state (fake null) even when the C# reference exists, so null checks must use Unity's overloaded== nullbehavior. - UnityWeakReference: It casts
TargettoT(a Unity object) and checks that result with Unity's null semantics, then exposesIsAliveandTryGetTargetaccordingly.
Examples
Basic usage (TryGetTarget)
var weak = new UnityWeakReference<SomeBehaviour>(obj);
// Later...
if (weak.TryGetTarget(out var target))
{
target.DoSomething(); // Use only while still valid
}
Invalidation after Destroy
var wr = new UnityWeakReference<GameObject>(go);
Debug.Assert(wr.IsAlive && wr.TryGetTarget(out var t));
Destroy(go);
Debug.Assert(!wr.IsAlive && !wr.TryGetTarget(out _));
WeakDelegate
- Role: Holds only the delegate target weakly. When the target is GC'd (plain) or destroyed (Unity object),
TryGetDelegate()returns null. - Restriction: Lambda and anonymous methods are not allowed. Use method groups or named delegates only.
- Unity: When the target is a
UnityEngine.Object, usesUnityWeakReferenceinternally so destroyed objects are treated as dead (#if UNITY_2017_1_OR_NEWER).
Members
IsAlive: true if the method is static or the target is alive.TryGetDelegate(): Returns the delegate when alive, null otherwise.
var wd = new WeakDelegate<Action>(handler.OnCalled);
var d = wd.TryGetDelegate();
d?.Invoke();
// When handler is GC'd (plain) or destroyed (Unity object), TryGetDelegate() == null
WeakAction / WeakAction
- Role: Holds an
ActionorActionweakly.Invoke()runs only when the target is alive; otherwise no-op. - Restriction: Lambda and anonymous methods are not allowed. Uses
WeakDelegateinternally. - Overloads: No-arg
WeakAction, one-argWeakAction... up toWeakAction.
Members: IsAlive, Invoke() / Invoke(arg1, ...) (up to 10 args).
wa.Invoke(); // Runs if target is alive; otherwise does nothing
WeakEvent / WeakEvent
- Role: Subscribe, unsubscribe, and raise multiple
Actionhandlers with weak references. Dead references are removed on Raise. - Restriction: Lambda and anonymous methods are not allowed. Not thread-safe (same thread or caller synchronization required).
- Overloads: No-arg
WeakEvent, one-argWeakEvent... up toWeakEvent.
Members: Subscribe, Unsubscribe, Raise / Raise(arg1, ...), + / - operators.
evt.Subscribe(handler.OnRaise);
evt.Raise();
evt.Unsubscribe(handler.OnRaise);
WeakEventSource
- Role: Weak event following the
EventHandlerpattern. Passes sender andTEventArgs. - Restriction: Lambda and anonymous methods are not allowed. Not thread-safe.
Members: Subscribe, Unsubscribe, Raise(object sender, TEventArgs args), + / - operators.
evt.Subscribe(handler.OnEvent);
evt.Raise(this, EventArgs.Empty);