Aot Trimming logo

Aot Trimming

CommunityPopular
Aaronontheweb
aot-trimming

Guidelines for making .NET libraries and applications trimming-safe and Native AOT compatible. Covers the trimming/AOT model, the MSBuild properties that enable analysis (IsTrimmable, IsAotCompatible, PublishTrimmed, PublishAot), the trimming attributes (RequiresUnreferencedCode, RequiresDynamicCode, DynamicallyAccessedMembers, UnconditionalSuppressMessage), IL2xxx/IL3xxx warning codes, and a pattern playbook: source generators and UnsafeAccessor, generated-interception diagnostic suppressors, intentional runtime scanning boundaries, trimming-safe islands and feature switches, migration analyzers, and temporary warning-approval baselines. Use when introducing trimming/AOT support, resolving IL2xxx/IL3xxx warnings, or making reflection-heavy code trimming-safe in C# / .NET codebases. For System.Text.Json AOT scenarios, see also the serialization skill.

Overview

PublisherAaronontheweb
Repositorydotnet-skills
Skill nameaot-trimming
Stars
1.2K
Forks
110
Bundled files
2
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 2 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Aaronontheweb on GitHub. Read the source before you install it.

Installation

Install the Aot Trimming AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Aaronontheweb/dotnet-skills/tree/master/skills/aot-trimming
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Aaronontheweb/dotnet-skills.git /tmp/dotnet-skills
mkdir -p .claude/skills
cp -r /tmp/dotnet-skills/skills/aot-trimming .claude/skills/aot-trimming
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Aot Trimming in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Aot Trimming on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Aot Trimming is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

.NET Trimming and Native AOT

When to Use

  • Adding trimming (PublishTrimmed) or Native AOT (PublishAot) support to a library or application
  • Resolving IL2xxx (trimming) and IL3xxx (AOT/single-file) warnings
  • Making reflection-heavy code trimming-safe
  • Replacing runtime reflection, assembly scanning, or Reflection.Emit with compile-time alternatives
  • Reviewing a codebase or PR for trimming/AOT compatibility, including generated interceptors and analyzer suppressors
  • Designing public APIs that must carry trimming annotations

Core Goals

  • Produce code that survives the linker's reachability analysis and runs under Native AOT (no JIT).
  • Express what the compiler cannot see — which members reflection needs — using the trimming attributes.
  • Keep trimming-safe and trimming-unsafe code separated so unsafe paths are explicit and contained.

Core Model

Trimming vs Native AOT

  • Trimming (PublishTrimmed) runs ILLink reachability analysis: only statically reachable code is kept. Reflection the analyzer cannot see is trimmed away and produces warnings.
  • Native AOT (PublishAot) makes trimming mandatory and removes the JIT. Reflection.Emit is unsupported, constructing unknown generic instantiations at runtime is not guaranteed, Expression.Compile() may fall back to interpretation, and reflection works only over members the linker preserved.
  • Both run the same static analysis:
    • IL2xxx — trimming warnings: RequiresUnreferencedCode propagation + DynamicallyAccessedMembers dataflow.
    • IL3xxx — AOT and single-file warnings: RequiresDynamicCode / RequiresAssemblyFiles.

A library can be trimmable but not AOT-compatible (it uses Reflection.Emit, which trimming tolerates but AOT does not).

The two problems to solve

  1. Unseen reflection (trimming): the linker removes a member or type you load by name, or reach through typeof(T) in a generic method.
  2. Dynamic code generation (AOT): the JIT is gone, so Reflection.Emit is unsupported and constructing unknown generic instantiations is not guaranteed.

Both are solved the same way: make the required members statically visible to the analyzer, or remove the need for reflection entirely.

Reflection is not the enemy

Reflection is a legitimate technique and is completely fine in ordinary JIT applications — you need none of this there. The trimming attributes exist to allow reflection under trimming/AOT, not to forbid it: annotate what reflection needs, and the linker keeps it. Reach for source generators, [UnsafeAccessor], or capability gating only when you want a specific reflective surface removed or contained (for example, a public registration API you do not want to ship with [RequiresUnreferencedCode]).

Project Configuration

For a library, declare compatibility so the analyzers surface warnings and consumers know:

xml
<PropertyGroup>
  <!-- Trim-compatible: enables trim warnings. -->
  <IsTrimmable>true</IsTrimmable>

  <!-- AOT-compatible: implies IsTrimmable + EnableTrimAnalyzer + EnableSingleFileAnalyzer + EnableAotAnalyzer. -->
  <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>

For an application, publish trimmed or AOT:

xml
<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
  <!-- or -->
  <PublishAot>true</PublishAot>
</PropertyGroup>

Verify by actually publishing — a trimmed/AOT app must produce zero warnings:

bash
dotnet publish -c Release -r <rid> -p:PublishTrimmed=true
dotnet publish -c Release -r <rid> -p:PublishAot=true

A test app that roots the library is the standard way to validate a library:

xml
<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
<ItemGroup>
  <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
  <TrimmerRootAssembly Include="MyLibrary" />
</ItemGroup>

See aot-trimming-playbook-reference.md for the full property table, feature switches, and the warning-approval baseline pattern.

The Attribute Model

These live in System.Diagnostics.CodeAnalysis. See trimming-attributes-reference.md for the full catalog with exact signatures, warning codes, and rules.

  • [RequiresUnreferencedCode(message)] — marks code that needs members the linker cannot see. Suppresses warnings inside; emits IL2026 at every call site.
  • [RequiresDynamicCode(message)] — marks code that needs a JIT (emitting, dynamic). Emits IL3050 at call sites.
  • [DynamicallyAccessedMembers(memberTypes)] — declares which members of a Type/string must be preserved. Flows backward from the reflection site to the Type source. Scope it (and the other attributes) to a single accessor by placing the attribute directly on the accessor, or with the [method:], [field:], or [return:] targets.
  • [UnconditionalSuppressMessage(category, checkId, Justification = "...")] — last-resort, IL-persisted suppression; must carry a justification and a real invariant.
  • [DynamicDependency("Member", typeof(T))] — keeps named members but does not silence warnings.

Choose the narrowest DynamicallyAccessedMemberTypes for the reflection you actually perform:

ReflectionMember type
Activator.CreateInstance<T>(), new T(), Activator.CreateInstance(type)PublicParameterlessConstructor
DI activation (ActivatorUtilities.CreateFactory<T>)PublicConstructors
Explicit non-public activationNonPublicConstructors
type.GetInterfaces()Interfaces
type.GetMethods()PublicMethods (add NonPublicMethods when needed)
type.GetProperties()PublicProperties
type.GetFields()PublicFields
type.GetNestedTypes()PublicNestedTypes

The Pattern Playbook (selection)

First decide whether the code must be trimming/AOT safe at all. A plain JIT app or library needs none of this — reflection is fine there. Only when you or a consumer publish trimmed/AOT does reflection need to become visible to the linker.

The goal is never "remove all reflection"; it is make the reflection statically visible so the linker keeps what it needs. Choose per call site:

  • Keep the reflection and annotate it — the common case. Put [DynamicallyAccessedMembers(...)] on the Type/string source and propagate it through the chain; mark entry points [RequiresUnreferencedCode]/[RequiresDynamicCode]. The attributes exist precisely to let reflection work under trimming. See trimming-attributes-reference.md.

  • Replace the reflection with a compile-time alternative — when you want a reflective surface gone (for example, a public registration API you do not want to ship with [RequiresUnreferencedCode]). A source generator emits direct type references (marker attribute → generated registration); [UnsafeAccessor] replaces FieldInfo/MethodInfo/ConstructorInfo. See aot-trimming-playbook-reference.md and aot-trimming-playbook-reference.md.

  • Isolate a whole dynamic path — when a path is inherently dynamic (emitting proxies, loading plugins by name), move it behind a boundary and provide a trimming-safe alternative selected by RuntimeFeature.IsDynamicCodeSupported. See aot-trimming-playbook-reference.md.

  • Suppress only as a last resort — a single [UnconditionalSuppressMessage(...)] at an invariant-proven leaf, with a Justification. Never #pragma (not persisted in IL).

See aot-trimming-playbook-reference.md for the full playbook, including object-overload → generic-overload redirects, generated-interception suppressors, intentional scanning boundaries, migration analyzers, strict-mode precedence, and temporary warning-approval baselines.

Reference Files

  • trimming-attributes-reference.md: The complete attribute catalog — RequiresUnreferencedCode, RequiresDynamicCode, DynamicallyAccessedMembers (full DynamicallyAccessedMemberTypes list), UnconditionalSuppressMessage, DynamicDependency — each with intent, exact signature, and rules, plus the IL2xxx/IL3xxx warning-code table.
  • aot-trimming-playbook-reference.md: The full pattern playbook — source generators vs reflection, trimming-safe islands and capability guards, separating safe/unsafe code, System.Text.Json source generation, object-overload → generic-overload redirects with [OverloadResolutionPriority] and opt-in analyzers, migration analyzers, warning-approval baselines, known gotchas, and the full generation checklist.

Generation Checklist (Summary)

  1. Project — IsTrimmable/IsAotCompatible on libraries; PublishTrimmed/PublishAot on apps; zero warnings on publish.
  2. Make reflection visible (or replace it) — annotate the reflection you keep (DynamicallyAccessedMembers/RequiresUnreferencedCode/RequiresDynamicCode); replace it with source generators, generic (typeof(T)) APIs, or [UnsafeAccessor] only where you want the reflective surface gone.
  3. Annotate — narrowest DynamicallyAccessedMembers on every Type/string source; propagate through the chain; [RequiresUnreferencedCode]/[RequiresDynamicCode] on unsafe entry points.
  4. Isolate — keep unsafe reflective/emit code behind a boundary; gate dynamic-code paths with RuntimeFeature.IsDynamicCodeSupported, and separate trimming paths with #if or annotations.
  5. Suppress — only with [UnconditionalSuppressMessage] + justification at invariant-proven leaves; never #pragma/SuppressMessage.
  6. Verify — publish trimmed and AOT, run analyzers, and lock the warning surface with an approval baseline.

References

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Aot Trimming AI skill do?

Guidelines for making .NET libraries and applications trimming-safe and Native AOT compatible. Covers the trimming/AOT model, the MSBuild properties that enable analysis (IsTrimmable, IsAotCompatible, PublishTrimmed, PublishAot), the trimming attributes (RequiresUnreferencedCode, RequiresDynamicCode, DynamicallyAccessedMembers, UnconditionalSuppressMessage), IL2xxx/IL3xxx warning codes, and a pattern playbook: source generators and UnsafeAccessor, generated-interception diagnostic suppressors, intentional runtime scanning boundaries, trimming-safe islands and feature switches, migration ana...

Why use Aot Trimming on TypingMind?

Because you install it once and use it with any model. Aot Trimming is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Aot Trimming in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Aaronontheweb/dotnet-skills/tree/master/skills/aot-trimming. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Aot Trimming?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Aot Trimming?

As many as you like. As long as a model supports skills, you can use Aot Trimming with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Aot Trimming AI skill free?

Yes. It is published on GitHub by Aaronontheweb under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇