Quick answer: Reference the generic instantiation once in code (e.g., new List<MyType>()) so IL2CPP sees it statically, or add a link.xml preserve entry for the constructed type.

Your game runs fine in the Editor and in the Mono build. The IL2CPP iOS build throws on startup: ExecutionEngineException: Attempting to call method 'System.Collections.Generic.List`1[MyType]::.ctor' for which no ahead of time (AOT) code was generated. The list works in C# anywhere else.

How IL2CPP Generic Stripping Works

IL2CPP converts your C# IL to C++ at build time. Generics require monomorphization: each instantiation (List<int>, List<MyType>, Dictionary<string, Player>) becomes a separate concrete C++ class. The build process walks the call graph and generates code only for the instantiations it can see.

If a generic instantiation is only created via:

...IL2CPP won’t see it during static analysis. The C++ class doesn’t exist. At runtime, the AOT framework throws.

Fix 1: Add a Static Reference

using System.Collections.Generic;
using UnityEngine;

public static class AotTypeHints
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void EnsureTypes()
    {
        var _ = new List<MyType>();
        var __ = new Dictionary<string, Player>();
        var ___ = new HashSet<Item>();
        System.GC.KeepAlive(_);
        System.GC.KeepAlive(__);
        System.GC.KeepAlive(___);
    }
}

The references force IL2CPP to generate code for these instantiations. GC.KeepAlive prevents the compiler from eliding them as dead. Place in Plugins/ or any non-Editor folder.

Fix 2: link.xml Preserve

Create Assets/link.xml:

<linker>
  <assembly fullname="Assembly-CSharp">
    <type fullname="System.Collections.Generic.List`1[[MyType, Assembly-CSharp]]" preserve="all" />
    <type fullname="System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[Player, Assembly-CSharp]]" preserve="all" />
  </assembly>
</linker>

The fully-qualified instantiated type names tell the linker to preserve these generic specializations. Tedious for many types; the static-reference approach scales better.

Fix 3: Lower Managed Stripping Level

For projects with many reflection-driven types and limited bandwidth to enumerate them: Player Settings → Other Settings → Managed Stripping Level → Low. Increases binary size 5–15% but eliminates stripping-related crashes broadly.

Don’t reach for this first — it’s the “turn it off” option. Use static references for known cases and Low only when the call graph genuinely can’t be enumerated.

Fix 4: Mark Methods with [Preserve]

If reflection calls a specific method on a generic instantiation, decorate it:

using UnityEngine.Scripting;

[Preserve]
public List<MyType> GetItems() => items;

Stops the linker from stripping the method even when it can’t see direct callers.

Verifying

Build IL2CPP for the target platform. Watch the device logs (Xcode console for iOS, adb logcat for Android). Reproduce the path that triggered the crash. ExecutionEngineException should not appear; the operation should succeed.

“IL2CPP needs to see your generics statically. Either reference them in code or list them in link.xml — otherwise they won’t exist in shipping.”

Build an AotTypeHints class as a project convention — add to it whenever you introduce a new reflection-loaded generic.