"A static Func/Action lambda orphaned by hotload throws NotImplementedException"
▸ SYMPTOM
After a code edit and hotload, the next Play session throws immediately:
System.NotImplementedException: Unable to find matching substitution for a lambda methodsurfacing as Exception when calling 'Start' or 'Update' on <Component> wherever the seam is read. The compile is green — nothing looks wrong until the frame that invokes the stored delegate. An editor restart makes it disappear, which is what makes it feel random.
▸ CAUSE
A static Func<>/Action<> field that holds a lambda survives hotload still pointing at the assembly that was just swapped out. Hotload copies static state across the reload, but it cannot substitute a lambda whose declaring assembly no longer exists. The stored delegate becomes an orphan that detonates the moment it is invoked.
Classic shape:
// Component exposes a static extension seam
public static Func<bool> SomeSeam;
// A bootstrap wires it at session start
SomeSeam = () => panel.SomeFlag;Edit any code, hotload, and the next Play reads SomeSeam() before the bootstrap has run again to re-assign it — so the invoke hits the orphaned lambda and throws.
▸ FIX
Two halves, both required:
1. The reader self-heals. Guard the invoke, null the stored delegate on the substitution failure, and fall back to the default. Whoever owns the seam re-wires it at session start anyway, so nulling is safe:
public static bool ReadSeam()
{
if (SomeSeam is null) return false;
try
{
return SomeSeam();
}
catch (NotImplementedException)
{
SomeSeam = null; // orphaned lambda from a prior assembly — drop it
return false; // return the default; the owner re-wires at session start
}
}2. Assigners always re-assign at session start rather than assuming the static carried over from the previous session. Never treat a static delegate as durable across a hotload.
▸ WHY IT WORKS
The try/catch (NotImplementedException) turns a hard crash into a one-frame no-op the first time an orphaned seam is read after a hotload; nulling the field means the next read short-circuits cleanly until the owner re-wires it. Combined with unconditional re-assignment at session start, the seam is correct whether or not the static survived — you no longer depend on hotload preserving something it fundamentally cannot preserve.
This is the stored-delegate case of the same lambda-substitution failure that can leave mixed assemblies after a hotload. An editor restart clears it too, but a defensive reader survives without one.
- Published