重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
dotnet-aot-compat by dotnet/skills
npx skills add https://github.com/dotnet/skills --skill dotnet-aot-compat使 .NET 项目与 Native AOT 和剪裁兼容,通过系统性地解决所有 IL 剪裁/AOT 分析器警告。
当项目仅面向不支持剪裁/AOT 分析器的 .NET Framework (net4x) 时,请勿使用此技能。
一个现有的面向 net8.0 或更高版本(或至少包含一个 net8.0+ TFM 的多目标)的 .NET 项目,并已安装相应的 .NET SDK。
Native AOT 和 IL 剪裁器执行静态分析以确定哪些代码是可访问的。反射可能会破坏此分析,因为剪裁器无法看到运行时访问的类型/成员。IsAotCompatible 属性启用分析器,将这些问题标记为构建警告(ILXXXX 代码)。
#pragma warning disable。它会在构建时隐藏 Roslyn 分析器的警告,但 IL 链接器和 AOT 编译器仍然会看到该问题。代码将在剪裁/发布时失败。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
[UnconditionalSuppressMessage]。它告诉分析器和链接器忽略该警告,这意味着剪裁器无法验证安全性。在构建时引发错误总是优于隐藏问题并使其在运行时静默失败。[DynamicallyAccessedMembers] 注解,通过调用链传递类型信息。object[] 装箱 Type)。[RequiresUnreferencedCode] / [RequiresDynamicCode] / [RequiresAssemblyFiles] 将方法标记为从根本上与剪裁不兼容,将要求传播给调用者。这清晰地暴露了问题,而不是隐藏它——调用者必须明确承认不兼容性。剪裁器通过赋值、参数传递和返回值跟踪 [DynamicallyAccessedMembers] 注解。如果此流被破坏(例如,通过将 Type 装箱到 object 中、存储到非类型化集合中,或通过接口转换),剪裁器将失去跟踪并发出警告。修复方法是保持注解流,而不是抑制警告。
不要预先探索代码库。 构建警告会确切地告诉你哪些文件和行需要更改。遵循一个紧密的循环:构建 → 选择一个警告 → 在该行打开该文件 → 应用修复方案 → 重新构建。阅读或分析超出特定警告指向范围的源文件是浪费精力并会导致超时。让编译器引导你。
❌ 在构建之前,不要 运行
find、ls或grep来理解项目结构。不要 阅读 README、文档或架构文件。你的第一个操作应该是步骤 1(启用 AOT 分析),然后构建。
添加 IsAotCompatible。如果项目并非专门面向 net8.0+,请添加 TFM 条件(AOT 分析需要 net8.0+):
<PropertyGroup>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>
这会为兼容的 TFM 自动设置 EnableTrimAnalyzer=true 和 EnableAotAnalyzer=true。对于多目标项目(例如 netstandard2.0;net8.0),该条件确保在较旧的 TFM 上不会出现 NETSDK1210 警告。
dotnet build <project.csproj> -f <net8.0-or-later-tfm> --no-incremental 2>&1 | grep 'IL[0-9]\{4\}'
排序并去重。常见的警告代码:
[DynamicallyAccessedMembers] 的 Type 参数上进行反射调用Type 传递给需要 [DynamicallyAccessedMembers] 的方法Type.GetType(string)[RequiresUnreferencedCode] 的方法[DynamicallyAccessedMembers]Assembly.Location 返回空字符串[RequiresDynamicCode] 的方法将步骤 2 中的警告按警告代码分组并计数。暂时不要打开单个文件。 按数量识别前 1-2 种模式——这些决定了你的修复策略:
| 模式 | 典型修复方案 |
|---|---|
来自 JsonSerializer 的许多 IL2026 + IL3050 | 立即转到策略 C —— 创建一个 JsonSerializerContext,然后批量更新所有调用点 |
Type 参数上的 IL2070/IL2087 | 向最内层的方法添加 [DynamicallyAccessedMembers],然后向外层级联 |
传递未注解的 Type 导致的 IL2067 | 在源头注解参数 |
在大多数实际项目中,来自 JsonSerializer 的 IL2026/IL3050 占主导地位。 除非警告明细清楚地显示其他情况,否则从策略 C 开始。批量 JSON 修复后,使用策略 A–B 处理剩余的警告。仅将策略 D 作为最后手段。
从最内层的反射调用向外层工作。每个修复都可能向调用者级联新的警告。
保持警告驱动。 对于每个警告,只打开编译器报告的文件和行,识别模式,应用下面匹配的修复方案,然后继续。不要扫描代码库寻找类似模式或试图理解完整架构——修复编译器告诉你的内容,重新构建,让新的警告指导下一次更改。修复一小批警告(5-10 个),然后立即重新构建以检查进度。
在可用时使用子代理。 如果你可以启动子代理(例如通过 task 工具),并行分派多个子代理 同时编辑不同的文件。保持主循环专注于构建、解析警告和分派——将实际的文件编辑委托给子代理。对于批量 JSON 更新,给每个子代理一个提示,让其更新 5-10 个文件。经过 2 个构建-修复周期后,将所有剩余的文件编辑并行分派给子代理——不要继续顺序修复文件。 示例:
更新这些文件以使用源生成的 JSON:
src/Models/Resource.Serialization.cs、src/Models/Identity.Serialization.cs、src/Models/Plan.Serialization.cs。在每个文件中,将JsonSerializer.Serialize(writer, value)替换为JsonSerializer.Serialize(writer, value, MyProjectJsonContext.Default.TypeName),将JsonSerializer.Deserialize<T>(ref reader)替换为JsonSerializer.Deserialize(ref reader, MyProjectJsonContext.Default.TypeName)。只编辑 JsonSerializer 调用点。
[DynamicallyAccessedMembers](首选)当方法在 Type 参数上使用反射时,注解该参数以告诉剪裁器需要哪些成员:
using System.Diagnostics.CodeAnalysis;
// 之前(警告 IL2070):
void Process(Type t) {
var method = t.GetMethod("Foo"); // 剪裁器无法验证
}
// 之后(干净):
void Process([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {
var method = t.GetMethod("Foo"); // 剪裁器保留公共方法
}
当你注解一个参数时,所有调用者现在必须传递正确注解的类型。这会向外层级联——跟踪每个调用者并根据需要注解或重构。调用者的注解必须至少包含与被调用者相同的成员类型。 如果被调用者需要 PublicConstructors | NonPublicConstructors,调用者必须指定相同或超集——仅使用 NonPublicConstructors 将产生 IL2091。
当注解流因装箱(将 Type 存储在 object、object[] 或非类型化集合中)而中断时,重构以直接传递 Type:
// 已破坏:Type 被装箱到 object[] 中,注解丢失
void Process(object[] args) {
Type t = (Type)args[0]; // IL2072:注解在装箱过程中丢失
Evaluate(t, ...);
}
// 已修复:将 Type 作为单独的、已注解的参数传递
void Process(
object[] args,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type calleeType,
...) {
Evaluate(calleeType, ...); // 注解流清晰
}
破坏流的常见模式及其修复方法:
object[] 参数包:将 Type 提取到一个专用的已注解参数中当大多数警告来自 JsonSerializer.Serialize/Deserialize 的 IL2026/IL3050 时,这是一个可以批量应用的单一机械修复:
收集受影响的类型 —— grep 所有 JsonSerializer.Serialize 和 JsonSerializer.Deserialize 调用点。提取正在序列化的类型(Deserialize<T> 中的 <T>,或 Serialize 中对象的运行时类型)。
创建一个 JsonSerializerContext,为找到的每个类型添加 [JsonSerializable]。跳过来自外部包的类型(例如,来自 Azure.Core 的 ResponseError)——你无法为你未拥有的类型生成源代码。通过下面的陷阱 #1 单独处理外部类型。
[JsonSerializerContext]
[JsonSerializable(typeof(ManagedServiceIdentity))]
[JsonSerializable(typeof(SystemData))]
// ... 为你拥有的每个类型添加一个属性
// 不要添加来自外部包的类型(例如,ResponseError)
internal partial class MyProjectJsonContext : JsonSerializerContext { }
3. 批量更新所有调用点 —— 不要单独读取每个文件。机械地应用模式:
* `JsonSerializer.Serialize(obj)` → `JsonSerializer.Serialize(obj, MyProjectJsonContext.Default.TypeName)`
* `JsonSerializer.Deserialize<T>(json)` → `JsonSerializer.Deserialize(json, MyProjectJsonContext.Default.TypeName)`
在一次传递中查找并更新所有调用点:
# 查找所有包含 JsonSerializer 调用的文件
grep -rl 'JsonSerializer\.\(Serialize\|Deserialize\)' src/ --include='*.cs'
然后使用顺序的 edit 调用将相同的转换应用于每个匹配的文件。不要对 C# 代码使用 sed —— 像 Deserialize<T>() 这样的泛型有尖括号和嵌套括号,sed 会弄乱它们。
[RequiresUnreferencedCode](最后手段)当一个方法从根本上需要无法静态描述的任意反射时:
[RequiresUnreferencedCode("使用 Assembly.Load 按名称加载插件")]
public void LoadPlugin(string assemblyName) {
var asm = Assembly.Load(assemblyName);
// ...
}
这会传播给调用者——它们也必须用 [RequiresUnreferencedCode] 注解。谨慎使用;它将整个调用链标记为与剪裁不兼容。
每修复一小批警告(5-10 个)后,使用 --no-incremental 重新构建并检查新警告。不要试图在重新构建前修复所有警告——频繁的重新构建可以及早发现错误并揭示级联警告。修复会级联——注解内部方法可能会在其调用者中引发警告。重复直到出现 0 Warning(s)。
构建所有目标框架以确保:
在 net8.0+ TFM 上出现 0 个 IL 警告
没有 NETSDK1210 警告(IsAotCompatible 条件会处理此问题)
在较旧的 TFM(netstandard2.0、net472 等)上构建干净
dotnet build <project.csproj> # 构建所有 TFM
[RequiresUnreferencedCode]。对于包含 netstandard2.0 或 net472 的多目标项目,你需要 DynamicallyAccessedMembersAttribute 及相关类型的 polyfills。请参阅 references/polyfills.md。
Azure.Core 的 ResponseError)并且它缺少源生成的序列化器时,Options.GetConverter<T>() 是基于反射的,将产生 IL 警告。首先检查该类型是否实现了 IJsonModel<T>(在 Azure SDK 中常见)——如果是,则完全绕过 JsonSerializer:// 之前(IL2026 —— JsonSerializer 使用反射):
JsonSerializer.Serialize(writer, errorValue);
// 之后(AOT 安全 —— 直接使用 IJsonModel):
((IJsonModel<ResponseError>)errorValue).Write(writer, ModelReaderWriterOptions.Json);
// 对于反序列化:
var error = ((IJsonModel<ResponseError>)new ResponseError()).Create(ref reader, ModelReaderWriterOptions.Json);
不要 将外部类型添加到你的 JsonSerializerContext 中——它不会为你未拥有的类型生成源代码。如果该类型没有实现 IJsonModel<T>,请编写一个带有手动 Utf8JsonReader/Utf8JsonWriter 逻辑的自定义 JsonConverter<T>,并通过你的上下文上的 [JsonSourceGenerationOptions] 注册它。
序列化库:大多数基于反射的序列化器(例如 Newtonsoft.Json、XmlSerializer)与 AOT 不兼容。迁移到基于源生成的序列化器,例如带有 JsonSerializerContext 的 System.Text.Json。如果迁移不可行,请用 [RequiresUnreferencedCode] 标记序列化调用点。
共享项目 / projitems:当源代码通过 <Import> 在多个项目之间共享时,添加到共享代码中的注解会影响所有消费项目。验证所有消费者是否仍然能干净地构建。
<IsAotCompatible>#pragma warning disable 或 [UnconditionalSuppressMessage]每周安装次数
48
代码仓库
GitHub 星标数
703
首次出现
Mar 10, 2026
安全审计
安装于
gemini-cli46
github-copilot46
amp46
codex46
kimi-cli46
opencode46
Make .NET projects compatible with Native AOT and trimming by systematically resolving all IL trim/AOT analyzer warnings.
Do not use this skill when the project exclusively targets .NET Framework (net4x), which does not support the trim/AOT analyzers.
An existing .NET project targeting net8.0 or later (or multi-targeting with at least one net8.0+ TFM) and the corresponding .NET SDK installed.
Native AOT and the IL trimmer perform static analysis to determine what code is reachable. Reflection can break this analysis because the trimmer can't see what types/members are accessed at runtime. The IsAotCompatible property enables analyzers that flag these issues as build warnings (ILXXXX codes).
#pragma warning disable for IL warnings. It hides warnings from the Roslyn analyzer at build time, but the IL linker and AOT compiler still see the issue. The code will fail at trim/publish time.[UnconditionalSuppressMessage]. It tells both the analyzer AND the linker to ignore the warning, meaning the trimmer cannot verify safety. Raising an error at build time is always preferable to hiding the issue and having it silently break at runtime.[DynamicallyAccessedMembers] annotations to flow type information through the call chain.Type through object[]).[RequiresUnreferencedCode] / [RequiresDynamicCode] / [RequiresAssemblyFiles] to mark methods as fundamentally incompatible with trimming, propagating the requirement to callers. This surfaces the issue clearly rather than hiding it — callers must explicitly acknowledge the incompatibility.The trimmer tracks [DynamicallyAccessedMembers] annotations through assignments, parameter passing, and return values. If this flow is broken (e.g., by boxing a Type into object, storing in an untyped collection, or casting through interfaces), the trimmer loses track and warns. The fix is to preserve the flow, not suppress the warning.
Do not explore the codebase up-front. The build warnings tell you exactly which files and lines need changes. Follow a tight loop: build → pick a warning → open that file at that line → apply the fix recipe → rebuild. Reading or analyzing source files beyond what a specific warning points you to is wasted effort and leads to timeouts. Let the compiler guide you.
❌ Do NOT run
find,ls, orgrepto understand the project structure before building. Do NOT read README, docs, or architecture files. Your first action should be Step 1 (enable AOT analysis), then build.
Add IsAotCompatible. If the project doesn't exclusively target net8.0+, add a TFM condition (AOT analysis requires net8.0+):
<PropertyGroup>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>
This automatically sets EnableTrimAnalyzer=true and EnableAotAnalyzer=true for compatible TFMs. For multi-targeting projects (e.g., netstandard2.0;net8.0), the condition ensures no NETSDK1210 warnings on older TFMs.
dotnet build <project.csproj> -f <net8.0-or-later-tfm> --no-incremental 2>&1 | grep 'IL[0-9]\{4\}'
Sort and deduplicate. Common warning codes:
Type parameter missing [DynamicallyAccessedMembers]Type to a method expecting [DynamicallyAccessedMembers]Type.GetType(string) with a non-constant argument[RequiresUnreferencedCode][DynamicallyAccessedMembers] required by constraintGroup the warnings from Step 2 by warning code and count them. Do not open individual files yet. Identify the top 1-2 patterns by count — these drive your fix strategy:
| Pattern | Typical fix |
|---|---|
Many IL2026 + IL3050 from JsonSerializer | Go to Strategy C immediately — create a JsonSerializerContext, then batch-update all call sites |
IL2070/IL2087 on Type parameters | Add [DynamicallyAccessedMembers] to the innermost method, then cascade outward |
IL2067 passing unannotated Type | Annotate the parameter at the source |
In most real projects, IL2026/IL3050 from JsonSerializer dominate. Start with Strategy C unless the warning breakdown clearly shows otherwise. After the batch JSON fix, handle remaining warnings with Strategies A–B. Only use Strategy D as a last resort.
Work from the innermost reflection call outward. Each fix may cascade new warnings to callers.
Stay warning-driven. For each warning, open only the file and line the compiler reported, identify the pattern, apply the matching fix recipe below, and move on. Do not scan the codebase for similar patterns or try to understand the full architecture — fix what the compiler tells you, rebuild, and let new warnings guide the next change. Fix a small batch of warnings (5-10), then rebuild immediately to check progress.
Use sub-agents when available. If you can launch sub-agents (e.g., via a task tool), dispatch multiple sub-agents in parallel to edit different files simultaneously. Keep the main loop focused on building, parsing warnings, and dispatching — delegate actual file edits to sub-agents. For batch JSON updates, give each sub-agent 5-10 files to update in one prompt. After 2 build-fix cycles, dispatch all remaining file edits to sub-agents in parallel — do not continue fixing files sequentially. Example:
Update these files to use source-generated JSON:
src/Models/Resource.Serialization.cs,src/Models/Identity.Serialization.cs,src/Models/Plan.Serialization.cs. In each file, replaceJsonSerializer.Serialize(writer, value)withJsonSerializer.Serialize(writer, value, MyProjectJsonContext.Default.TypeName)andJsonSerializer.Deserialize<T>(ref reader)withJsonSerializer.Deserialize(ref reader, MyProjectJsonContext.Default.TypeName). Only edit the JsonSerializer call sites.
[DynamicallyAccessedMembers] (preferred)When a method uses reflection on a Type parameter, annotate the parameter to tell the trimmer what members are needed:
using System.Diagnostics.CodeAnalysis;
// Before (warns IL2070):
void Process(Type t) {
var method = t.GetMethod("Foo"); // trimmer can't verify
}
// After (clean):
void Process([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {
var method = t.GetMethod("Foo"); // trimmer preserves public methods
}
When you annotate a parameter, all callers must now pass properly annotated types. This cascades outward — follow each caller and annotate or refactor as needed. The caller's annotation must include at least the same member types as the callee's. If the callee requires PublicConstructors | NonPublicConstructors, the caller must specify the same or a superset — using only NonPublicConstructors will produce IL2091.
When annotation flow is broken by boxing (storing Type in object, object[], or untyped collections), refactor to pass the Type directly:
// BROKEN: Type boxed into object[], annotation lost
void Process(object[] args) {
Type t = (Type)args[0]; // IL2072: annotation lost through boxing
Evaluate(t, ...);
}
// FIXED: Pass Type as a separate, annotated parameter
void Process(
object[] args,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type calleeType,
...) {
Evaluate(calleeType, ...); // annotation flows cleanly
}
Common patterns that break flow and how to fix them:
object[] parameter bags: Extract the Type into a dedicated annotated parameterWhen most warnings are IL2026/IL3050 from JsonSerializer.Serialize/Deserialize, this is a single mechanical fix applied in bulk:
Collect affected types — grep for all JsonSerializer.Serialize and JsonSerializer.Deserialize call sites. Extract the type being serialized (the <T> in Deserialize<T>, or the runtime type of the object in Serialize).
Create oneJsonSerializerContext with [JsonSerializable] for every type found. Skip types from external packages (e.g., ResponseError from Azure.Core) — they won't source-generate for types you don't own. Handle external types separately via Gotcha #1 below.
[JsonSerializerContext]
[JsonSerializable(typeof(ManagedServiceIdentity))]
[JsonSerializable(typeof(SystemData))]
// ... one attribute per type YOU OWN
// Do NOT add types from external packages (e.g., ResponseError)
internal partial class MyProjectJsonContext : JsonSerializerContext { }
3. Batch-update all call sites — do not read each file individually. Apply the pattern mechanically:
* `JsonSerializer.Serialize(obj)` → `JsonSerializer.Serialize(obj, MyProjectJsonContext.Default.TypeName)`
* `JsonSerializer.Deserialize<T>(json)` → `JsonSerializer.Deserialize(json, MyProjectJsonContext.Default.TypeName)`
Find and update all call sites in one pass:
# Find all files with JsonSerializer calls
grep -rl 'JsonSerializer\.\(Serialize\|Deserialize\)' src/ --include='*.cs'
Then use sequential edit calls to apply the same transformation to every matching file. Do not usesed for C# code — generics like Deserialize<T>() have angle brackets and nested parentheses that sed will mangle.
[RequiresUnreferencedCode] (last resort)When a method fundamentally requires arbitrary reflection that cannot be statically described:
[RequiresUnreferencedCode("Loads plugins by name using Assembly.Load")]
public void LoadPlugin(string assemblyName) {
var asm = Assembly.Load(assemblyName);
// ...
}
This propagates to callers — they must also be annotated with [RequiresUnreferencedCode]. Use sparingly; it marks the entire call chain as trim-incompatible.
After each small batch of fixes (5-10 warnings), rebuild with --no-incremental and check for new warnings. Do not attempt to fix all warnings before rebuilding — frequent rebuilds catch mistakes early and reveal cascading warnings. Fixes cascade — annotating an inner method may surface warnings in its callers. Repeat until 0 Warning(s).
Build all target frameworks to ensure:
0 IL warnings on net8.0+ TFMs
No NETSDK1210 warnings (the IsAotCompatible condition handles this)
Clean builds on older TFMs (netstandard2.0, net472, etc.)
dotnet build <project.csproj> # builds all TFMs
[RequiresUnreferencedCode].For multi-targeting projects that include netstandard2.0 or net472, you need polyfills for DynamicallyAccessedMembersAttribute and related types. See references/polyfills.md.
ResponseError from Azure.Core) and it lacks a source-generated serializer, Options.GetConverter<T>() is reflection-based and will produce IL warnings. First check if the type implements IJsonModel<T> (common in Azure SDK) — if so, bypass JsonSerializer entirely:// Before (IL2026 — JsonSerializer uses reflection):
JsonSerializer.Serialize(writer, errorValue);
// After (AOT-safe — uses IJsonModel directly):
((IJsonModel<ResponseError>)errorValue).Write(writer, ModelReaderWriterOptions.Json);
// For deserialization:
var error = ((IJsonModel<ResponseError>)new ResponseError()).Create(ref reader, ModelReaderWriterOptions.Json);
Do not add the external type to your JsonSerializerContext — it won't source-generate for types you don't own. If the type doesn't implement IJsonModel<T>, write a custom JsonConverter<T> with manual Utf8JsonReader/Utf8JsonWriter logic and register it via [JsonSourceGenerationOptions] on your context.
Serialization libraries : Most reflection-based serializers (e.g., Newtonsoft.Json, XmlSerializer) are not AOT-compatible. Migrate to a source-generation-based serializer such as System.Text.Json with a JsonSerializerContext. If migration is not feasible, mark the serialization call site with [RequiresUnreferencedCode].
Shared projects / projitems : When source is shared between multiple projects via <Import>, annotations added to shared code affect ALL consuming projects. Verify that all consumers still build cleanly.
Limitations Conceptual: Understanding trimming How-to: trim compat
<IsAotCompatible> with TFM condition to .csproj#pragma warning disable or [UnconditionalSuppressMessage] used for any IL warningWeekly Installs
48
Repository
GitHub Stars
703
First Seen
Mar 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
gemini-cli46
github-copilot46
amp46
codex46
kimi-cli46
opencode46
TanStack Query v5 完全指南:React 数据管理、乐观更新、离线支持
2,500 周安装
Assembly.Location returns empty string in single-file/AOT apps[RequiresDynamicCode]