Skip to content
Development
Skill

/native-aot

Native AOT, trimming, and single-file publishing for .NET 10: PublishAot, IsAotCompatible analyzers, source-generated JSON/regex/logging/config binding, fixing IL2xxx/IL3xxx trim and AOT warnings, per-RID publishing, and when NOT to use AOT. Load when the user mentions "Native

From plugin
dotnet-claude-kit
81752 skills11 agents2 hooks1 MCP

Claude Code

$ npx -y skills add codewithmukesh/dotnet-claude-kit --skill native-aot --agent claude-code

SKILL.md

native-aot.SKILL.md
name: native-aot
description: >
  Native AOT, trimming, and single-file publishing for .NET 10: PublishAot,
  IsAotCompatible analyzers, source-generated JSON/regex/logging/config binding,
  fixing IL2xxx/IL3xxx trim and AOT warnings, per-RID publishing, and when NOT
  to use AOT. Load when the user mentions "Native AOT", "PublishAot", "trimming",
  "PublishTrimmed", "single-file", "IL2026", "IL3050", "trim warning",
  "startup time", "self-contained", or "JsonSerializerContext".

Native AOT, Trimming & Single-File (.NET 10)

Core Principles

1. **Decide early** — AOT constrains library choice (no runtime reflection or code generation). Adding it to a mature reflection-heavy app is a rewrite. Adding it on day one costs almost nothing. 2. **A trim/AOT warning is a future runtime crash** — IL2xxx (trimming) and IL3xxx (AOT/single-file) warnings mark code that may throw `MissingMethodException` or silently misbehave in the published binary. Treat them as errors and keep the count at zero. 3. **Source generators replace reflection** — JSON, regex, logging, configuration binding, options validation, and MVVM all have generators. They keep the AOT path fast and analyzable. 4. **Test the published artifact, not `dotnet run`** — `dotnet run` uses the JIT and full metadata, so it hides every trimming bug. Smoke-test the binary that `dotnet publish` produces, in CI. 5. **AOT is a trade, not a free upgrade** — You get fast startup, small memory, and no runtime install. You lose dynamic loading, `Reflection.Emit`, and some steady-state throughput from Dynamic PGO. Pick it where those trade-offs pay.

Patterns

Project Setup

<!-- App (executable) project -->
<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <PublishAot>true</PublishAot>                    <!-- also turns on AOT analyzers during build -->
  <InvariantGlobalization>true</InvariantGlobalization> <!-- drop ICU if you don't need cultures -->
  <OptimizationPreference>Size</OptimizationPreference> <!-- or Speed -->
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<!-- Every class library the app references -->
<PropertyGroup>
  <IsAotCompatible>true</IsAotCompatible> <!-- implies IsTrimmable + trim/AOT/single-file analyzers -->
</PropertyGroup>
dotnet publish -c Release -r linux-x64   # one RID per publish

**Why:** putting `PublishAot` in the project file (not only on the command line) runs the analyzers on every build, so warnings appear while you code. `IsAotCompatible` on libraries surfaces problems at the source instead of in the app. Native AOT needs the platform toolchain (VS "Desktop development with C++" on Windows, `clang` + `zlib` dev packages on Linux, Xcode CLI tools on macOS). Build each OS on that OS in a CI matrix. A Linux binary runs only on the same or newer distro version it was built on.

Source-Generated JSON

public sealed record OrderDto(Guid Id, string Customer, decimal Total, DateTimeOffset CreatedAt);

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(OrderDto))]
[JsonSerializable(typeof(List<OrderDto>))]
internal sealed partial class AppJsonContext : JsonSerializerContext;

// Usage — pass the generated JsonTypeInfo, never a bare Type
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.OrderDto);
var orders = await JsonSerializer.DeserializeAsync(stream, AppJsonContext.Default.ListOrderDto, ct);
// ASP.NET Core minimal APIs: register the context once
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.ConfigureHttpJsonOptions(o =>
    o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

**Why:** reflection-based `System.Text.Json` is disabled in trimmed/AOT apps. The generated metadata is also faster on the JIT. Register every type that crosses the wire, including collection shapes (`List<OrderDto>`). `CreateSlimBuilder` leaves out non-AOT-friendly defaults. The minimal API Request Delegate Generator turns on automatically with `PublishAot`.

Generated Regex, Logging, Config Binding, Options Validation

internal static partial class Patterns
{
    [GeneratedRegex(@"^ORD-\d{6}$", RegexOptions.CultureInvariant)]
    internal static partial Regex OrderNumber { get; }
}

internal static partial class Log
{
    [LoggerMessage(Level = LogLevel.Information, Message = "Imported {Count} orders in {ElapsedMs} ms")]
    internal static partial void Imported(ILogger logger, int count, long elapsedMs);
}

[OptionsValidator]
internal sealed partial class ImportOptionsValidator : IValidateOptions<ImportOptions>;

public sealed class ImportOptions
{
    [Range(1, 10_000)] public int BatchSize { get; set; } = 500;
}
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>

**Why:** `[GeneratedRegex]` emits matching code at compile time (no `RegexOptions.Compiled`, which needs runtime IL emit). `[LoggerMessage]` avoids boxing and parsing the template on every call. The configuration binding generator replaces the reflection inside `Bind`/`Get<T>`/`Configure<T>`. `[OptionsValidator]` replaces reflection-based data-annotation validation.

Fixing Trim Warnings Honestly

// Propagate the requirement to callers instead of hiding it
[RequiresUnreferencedCode("Loads plugin types by name; not trim-safe.")]
[RequiresDynamicCode("Creates generic types at runtime.")]
public static object CreatePlugin(string typeName) => Activator.CreateInstance(Type.GetType(typeName)!)!;

// Or tell the trimmer exactly what to keep
public static T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>()
    where T : new() => new();

public static object CreateFrom(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) =>
    Activator.CreateInstance(type)!;

*

Read more
@codewithmukesh
Ships withdotnet-claude-kit

Make Claude Code a .NET 10 Expert

81752 skills11 agents1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
817
Star history of codewithmukesh/dotnet-claude-kit on GitHub, a chart by star-history.com

Other skills on dotnet-claude-kit.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about native-aot.

Which coding agents can use native-aot?
native-aot installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.