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<T>, respecting Unity null semantics).
Provided types: UnityWeakReference<T>, WeakDelegate<TDelegate>, WeakAction/WeakAction<T1..T10>, WeakEvent/WeakEvent<T1..T10>, WeakEventSource<TEventArgs>. See the Type Summary and sections below for details.
- 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:
UnityWeakReference<T>supports onlyT : UnityEngine.Object. For plain C# objects use .NETWeakReference<T>or 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.
- 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 | Purpose |
|---|---|
| UnityWeakReference<T> | Weak reference for UnityEngine.Object only. Treats destroyed objects as "dead" using Unity null semantics. |
| WeakDelegate<TDelegate> | Holds the delegate target weakly. TryGetDelegate() returns the delegate only when the target is alive. |
| WeakAction / WeakAction<T1..T10> | Holds an Action or Action<T1..T10> weakly. Invoke() runs only when the target is alive. |
| WeakEvent / WeakEvent<T1..T10> | Weak event based on Action. Subscribers that are GC'd or destroyed are removed on Raise. |
| WeakEventSource<TEventArgs> | Weak event based on EventHandler<TEventArgs>. Sender/args pattern. |
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.
- 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.
Basic usage (TryGetTarget)
var obj = GetComponent<SomeBehaviour>();
var weak = new UnityWeakReference<SomeBehaviour>(obj);
// Later...
if (weak.TryGetTarget(out var target))
{
target.DoSomething(); // Use only while still valid
}Invalidation after Destroy
var go = new GameObject();
var wr = new UnityWeakReference<GameObject>(go);
Debug.Assert(wr.IsAlive && wr.TryGetTarget(out var t));
Destroy(go);
Debug.Assert(!wr.IsAlive && !wr.TryGetTarget(out _));- 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 handler = new MyHandler();
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- Role: Holds an
ActionorAction<T1..T10>weakly.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<T1>… up toWeakAction<T1..T10>.
Members: IsAlive, Invoke() / Invoke(arg1, ...) (up to 10 args).
var wa = new WeakAction(handler.OnRaise);
wa.Invoke(); // Runs if target is alive; otherwise does nothing- 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<T1>… up toWeakEvent<T1..T10>.
Members: Subscribe, Unsubscribe, Raise / Raise(arg1, ...), + / - operators.
var evt = new WeakEvent();
evt.Subscribe(handler.OnRaise);
evt.Raise();
evt.Unsubscribe(handler.OnRaise);- Role: Weak event following the
EventHandler<TEventArgs>pattern. Passes sender andTEventArgs. - Restriction: Lambda and anonymous methods are not allowed. Not thread-safe.
Members: Subscribe, Unsubscribe, Raise(object sender, TEventArgs args), + / - operators.
var evt = new WeakEventSource<EventArgs>();
evt.Subscribe(handler.OnEvent);
evt.Raise(this, EventArgs.Empty);