使用方法
- 复制安装命令,在自己的智能体环境安装前先检查源码包。
- 从原始文档核对所需工具、凭据和支持的宿主。
- 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill native-aot开始前需了解
- 安装不包含模型订阅、API 额度或访问文件的权限。
- 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。
Native AOT, Trimming & Single-File (.NET 10)
Core Principles
- 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.
- A trim/AOT warning is a future runtime crash — IL2xxx (trimming) and IL3xxx (AOT/single-file) warnings mark code that may throw
MissingMethodExceptionor silently misbehave in the published binary. Treat them as errors and keep the count at zero. - Source generators replace reflection — JSON, regex, logging, configuration binding, options validation, and MVVM all have generators. They keep the AOT path fast and analyzable.
- Test the published artifact, not `dotnet run` —
dotnet runuses the JIT and full metadata, so it hides every trimming bug. Smoke-test the binary thatdotnet publishproduces, in CI. - 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 publishWhy: 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)!;Why: [RequiresUnreferencedCode]/[RequiresDynamicCode] move the warning to the call site, where a caller can choose a safe path. [DynamicallyAccessedMembers] lets the trimmer keep exactly the members reflection needs. Use [UnconditionalSuppressMessage] only when you can explain in the justification why the code is provably safe.
Single-File Without AOT
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<PublishTrimmed>true</PublishTrimmed> <!-- optional; requires the same trim discipline -->
</PropertyGroup>// BAD in single-file: Assembly.Location is empty (IL3000)
var dir = Path.GetDirectoryName(typeof(Program).Assembly.Location);
// GOOD
var dir = AppContext.BaseDirectory;Why: single-file gives one-exe distribution while keeping the JIT and full reflection. Use it when a dependency isn't AOT-compatible but you still want an easy download. File-path APIs are the usual breakage.
Verifying in CI
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- run: dotnet publish src/Contoso.Cli -c Release -o out
- run: ./out/contoso --version && ./out/contoso import --input samples/orders.csv --dry-runWhy: only the published binary proves trimming kept what you need. A smoke command on each OS catches missing metadata, native library, and globalization problems before release.
Anti-patterns
Suppressing Warnings to Get a Green Build
<!-- BAD — hides the crash until production -->
<NoWarn>$(NoWarn);IL2026;IL3050</NoWarn>
<TrimMode>partial</TrimMode>// GOOD — fix the cause: generated serializer instead of reflection
JsonSerializer.Serialize(order, AppJsonContext.Default.OrderDto);Reflection-Based Serialization Overloads
// BAD — IL2026/IL3050: needs runtime reflection over OrderDto
var json = JsonSerializer.Serialize(order);
var dto = await response.Content.ReadFromJsonAsync<OrderDto>(ct);
// GOOD — JsonTypeInfo overloads
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.OrderDto);
var dto = await response.Content.ReadFromJsonAsync(AppJsonContext.Default.OrderDto, ct);Convention-Based Assembly Scanning
// BAD — trimmer removes "unused" types; GetTypes() finds nothing at runtime
foreach (var t in typeof(Program).Assembly.GetTypes().Where(t => t.IsAssignableTo(typeof(ICommandHandler))))
services.AddTransient(typeof(ICommandHandler), t);
// GOOD — explicit registration (or a source generator that emits it)
services.AddTransient<ICommandHandler, ImportHandler>();
services.AddTransient<ICommandHandler, ExportHandler>();RegexOptions.Compiled and Runtime Expression Compilation
// BAD — needs Reflection.Emit; under AOT it falls back to slower interpretation
private static readonly Regex Sku = new(@"^[A-Z]{3}-\d+$", RegexOptions.Compiled);
// GOOD
[GeneratedRegex(@"^[A-Z]{3}-\d+$")]
private static partial Regex Sku { get; }Decision Guide
| Scenario | Recommendation |
|---|---|
| CLI tool shipped to non-.NET users | Native AOT per RID |
dotnet run app.cs script published | Native AOT is already the default for file-based apps |
| Serverless / scale-to-zero / many small containers | Native AOT (fast cold start, low memory); see container-publish for -aot base images |
| Avalonia desktop app | Native AOT viable with compiled bindings + trim-safe view mapping |
| WPF or WinForms | No trimming/AOT support; use ReadyToRun + single-file for startup |
| Long-running, high-throughput API | Usually JIT (Dynamic PGO wins at steady state); measure before switching |
| EF Core data access | EF Core AOT (precompiled queries) is still experimental; stay JIT for EF-heavy apps |
| MVC controllers, SignalR-heavy, Blazor Server | Not AOT targets; use minimal APIs if AOT is required |
Plugins / Assembly.LoadFrom / Reflection.Emit | Not possible under AOT; use JIT |
| One-exe download, AOT-incompatible deps | PublishSingleFile + SelfContained (JIT) |
| Library meant for AOT consumers | IsAotCompatible + zero warnings, multi-target net8.0+ |
| Dependency emits trim warnings | Replace it, or isolate behind [RequiresUnreferencedCode]; don't blanket-suppress |

