Quick answer: Force the missing generic specialization at compile time. Use AotEnsureMethodGenerated dummy calls, or pre-instantiate generics in a static class.
An IL2CPP build crashes on iOS: ExecutionEngineException: Attempting to call method ‘System.Collections.Generic.List`1[CustomStruct]::ctor’. AOT didn’t generate that specialization.
Why AOT Misses Generics
JIT generates generic specializations at runtime; AOT must enumerate all needed up front. If the IL2CPP build analysis doesn’t see your generic in use, it’s not generated. Reflection-only paths are common offenders.
Force Specialization
[Preserve]
public static class GenericPreservation
{
private static void EnsureGenerics()
{
_ = new List<CustomStruct>();
_ = new Dictionary<int, CustomStruct>();
_ = JsonUtility.FromJson<CustomStruct>("");
}
}
Dummy method referencing each generic instantiation. AOT generates these specializations. Method never executes; static analysis picks it up.
link.xml
<linker>
<assembly fullname="Assembly-CSharp">
<type fullname="System.Collections.Generic.List`1[[Game.CustomStruct]]" preserve="all" />
</assembly>
</linker>
Explicitly preserve the generic instantiation. Linker holds onto it through stripping.
Avoid Runtime Generics
For hot paths, consider non-generic alternatives:
- Use a strongly-typed wrapper class instead of List<Generic>.
- Or use the value-type-safe Span<T> / ReadOnlySpan<T> where possible.
Verifying
Build for iOS. Run; no ExecutionEngineException. Library/il2cpp_cache StrippingReport shows generics included.
“AOT specializes only what it can see. Force the missing ones via dummy references or link.xml.”
JSON deserialization (Newtonsoft) is a common source. Pre-instantiate JsonConvert.DeserializeObject<T> for all DTOs in a startup class.