Making Your .NET Application Trim and AOT Ready
IL trimming removes unused code from your published application, reducing deployment size. Native AOT goes further — it compiles your application to a native binary with no JIT, no IL, and no runtime code generation. Both require your code (and its dependencies) to be analysable at build time. If your application uses reflection, dynamic loading, or runtime code generation, you'll need to make changes.
Enabling trimming
Add these properties to your project file:
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
</PropertyGroup>
TrimMode="full" trims all assemblies, including framework ones. The alternative, TrimMode="partial", only trims assemblies that explicitly opt in with [AssemblyMetadata("IsTrimmable", "True")].
Publish with:
dotnet publish -c Release
Enabling Native AOT
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Native AOT implies trimming — you don't need both properties. Publish with the same command, but note that AOT compilation requires platform-specific toolchains (MSVC on Windows, clang on Linux).
The trimmer's enemy: reflection
The trimmer analyses your code statically. It follows method calls, type references, and constructor invocations to determine what's reachable. Reflection defeats this analysis because the trimmer can't know at build time what types Type.GetType("MyApp.SomeClass") will resolve to.
Common reflection patterns that cause trimming failures:
// The trimmer can't see that MyService is needed
var type = Type.GetType("MyApp.Services.MyService");
var instance = Activator.CreateInstance(type!);
// The trimmer may remove the property
var prop = typeof(Order).GetProperty("Total");
// Dynamic assembly loading
Assembly.LoadFrom("plugin.dll");
Trim warnings
The compiler emits warnings for code patterns that are incompatible with trimming. Enable them:
<PropertyGroup>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
Key warning codes to watch for:
- IL2026 — Member with
RequiresUnreferencedCodeAttributeis called - IL2057 —
Type.GetTypewith a string argument - IL2067 — Argument passed to parameter with
DynamicallyAccessedMembersAttributedoesn't satisfy the attribute - IL2075 — Calling
GetMethod/GetPropertyon a type not annotated for reflection
Annotating your code
When you genuinely need reflection, annotate types so the trimmer preserves them:
public class PluginLoader
{
public T CreatePlugin<[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicConstructors)] T>()
where T : class
{
return (T)Activator.CreateInstance(typeof(T))!;
}
}
The DynamicallyAccessedMembers attribute tells the trimmer to preserve the specified members on the type argument.
For methods that fundamentally require runtime code generation:
[RequiresUnreferencedCode("This method uses reflection to discover handlers.")]
[RequiresDynamicCode("This method generates types at runtime.")]
public void RegisterHandlers()
{
// Reflection-heavy code
}
These attributes propagate warnings to callers, making the trim-incompatibility visible up the call chain.
Source generators: the AOT-friendly alternative
The .NET ecosystem is systematically replacing reflection-based patterns with source generators:
| Reflection-based | Source-generated alternative |
|---|---|
System.Text.Json (default) |
JsonSerializerContext source gen |
| Configuration binding | Configuration binding source gen |
Logging (ILogger) |
LoggerMessage source gen |
| Regex | GeneratedRegex |
HttpClientFactory |
Request/response type registration |
Adopting these source generators is the single most impactful step for AOT readiness.
Checking library compatibility
Not all NuGet packages are trim-compatible. Check a package's status:
- Look for
IsTrimmablein the package metadata - Check the package's documentation for AOT support
- Build with trimming enabled and check for warnings referencing the package
The dotnet platform libraries are fully annotated. Popular third-party libraries vary — check their GitHub issues for AOT tracking issues.
Testing a trimmed application
Trimming bugs are runtime failures — a method that was trimmed away will throw a MissingMethodException or TypeLoadException. Your test strategy should include:
- Build with all warnings as errors for trim-related codes:
<PropertyGroup>
<WarningsAsErrors>IL2026;IL2057;IL2067;IL2075</WarningsAsErrors>
</PropertyGroup>
Run integration tests against the published output, not the debug build. Trimming only happens during
dotnet publish.Test all code paths, not just the happy path. A rarely-used error handler that uses reflection will only fail when that error actually occurs.
The road to AOT
A practical adoption path:
- Enable trim warnings and fix them — this is non-destructive and works in your normal build.
- Switch to source generators for JSON, logging, and configuration.
- Publish with trimming and run your full test suite against the trimmed output.
- Once clean, enable
PublishAotand address any remainingRequiresDynamicCodewarnings.
Not every application can be AOT-compiled today. EF Core, for instance, uses extensive runtime code generation and isn't fully AOT-compatible. But preparing your code for trimming — even if you never actually publish trimmed — improves code quality by making dependencies explicit and eliminating hidden reflection.