Quick answer: Add a linker.xml with explicit <type fullname="..." preserve="all" /> entries for any class loaded by name. Reference the file via <TrimmerRootDescriptor> in the .csproj. Or apply [DynamicallyAccessedMembers] on the call site.

Editor runs your inventory system, all good. Export build crashes with “Type ‘Game.Items.Sword’ not found.” The trimmer didn’t see the type used statically and stripped it from the assembly. C# in Godot 4 .NET ships with IL trimming on by default.

The Symptom

Editor and editor playtest work; export build throws TypeLoadException, MissingMethodException, or returns null from Assembly.GetType(name). Often the failure is a missing class or constructor that exists in source but not in the trimmed assembly.

What Causes This

The IL trimmer (linker) walks the assembly graph from the entry point and keeps only what’s reachable. Reflective access — Type.GetType("Foo"), JSON deserialization, scene tscn-loaded scripts — isn’t visible to the trimmer. The class compiles but is removed from the export.

The Fix Patterns

Pattern 1: Static reference in code. Cheapest for one or two types:

internal static class _KeepTypes
{
    internal static readonly Type[] _kept = new[]
    {
        typeof(Game.Items.Sword),
        typeof(Game.Items.Shield),
        typeof(Game.Enemies.Goblin),
    };
}

Each typeof roots the type so the trimmer keeps it.

Pattern 2: linker.xml. For many types, ship a descriptor:

<!-- linker.xml -->
<linker>
  <assembly fullname="YourGame">
    <type fullname="Game.Items.*" preserve="all" />
    <type fullname="Game.Enemies.*" preserve="all" />
  </assembly>
</linker>

Reference it in the .csproj:

<ItemGroup>
  <TrimmerRootDescriptor Include="linker.xml" />
</ItemGroup>

Pattern 3: Disable trimming for the assembly. In .csproj:

<PropertyGroup>
  <TrimMode>copy</TrimMode>
</PropertyGroup>

Bundles the entire assembly without trimming. Larger export, simpler. Good for game logic; reserve trimming for runtime libraries.

JSON Deserialization Trap

System.Text.Json with default reflection-based deserialization is the most common offender. The deserializer reflects over property setters; trimming removes them. Either:

Verifying

After export, decompile the resulting DLL with ILSpy or dotPeek. Confirm the class and members survived. If they didn’t, your descriptor doesn’t cover them or the .csproj reference is wrong.

Or run with --verbose and watch trimmer output during build — it logs which types are stripped.

“Static typeof. Or linker.xml. Or copy mode for the whole assembly. Reflective loads survive the export.”

Related Issues

For Godot resource load failures, see resource not found. For C# scripts not attaching, see Godot async/await.

Keep what reflection touches. Trimmer obeys descriptors.