dotnet-aot-compat

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dotnet-aot-compat

dotnet-aot-compat

Make .NET projects compatible with Native AOT and trimming by systematically resolving all IL trim/AOT analyzer warnings.
通过系统解决所有IL裁剪/AOT分析器警告,让.NET项目兼容Native AOT和裁剪功能。

When to Use This Skill

何时使用该技能

  • "Make this project AOT-compatible"
  • "Fix trimming warnings" or "fix IL warnings"
  • "Resolve IL2070 / IL2067 / IL2072 / IL2026 / IL3050 warnings"
  • "Add DynamicallyAccessedMembers annotations"
  • "Enable IsAotCompatible in my .csproj"
  • "My project has trim analyzer warnings after upgrading to net8.0"
  • "Annotate reflection code for the trimmer"
  • "让这个项目兼容AOT"
  • "修复裁剪警告""修复IL警告"
  • "解决IL2070 / IL2067 / IL2072 / IL2026 / IL3050警告"
  • "添加DynamicallyAccessedMembers注解"
  • "在我的.csproj中启用IsAotCompatible"
  • "我的项目升级到net8.0后出现裁剪分析器警告"
  • "为裁剪器给反射代码添加注解"

When Not to Use This Skill

何时不要使用该技能

Do not use this skill when the project exclusively targets .NET Framework (net4x), which does not support the trim/AOT analyzers.
当项目仅面向.NET Framework(net4x)时不要使用该技能,该框架不支持裁剪/AOT分析器。

Prerequisites

前置条件

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.
已有面向net8.0或更高版本的.NET项目(或多目标框架中至少包含一个net8.0+ TFM),且已安装对应的.NET SDK。

Background: What AOT Compatibility Means

背景:AOT兼容的含义

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).
Native AOT和IL裁剪器会执行静态分析来判断哪些代码是可触及的。反射会破坏该分析,因为裁剪器无法得知运行时会访问哪些类型/成员。
IsAotCompatible
属性会启用分析器,将这些问题标记为构建警告(ILXXXX编码)。

Critical Rules

关键规则

❌ Never suppress warnings incorrectly

❌ 永远不要错误地抑制警告

  • NEVER use
    #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.
  • NEVER use
    [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.
  • 绝对不要对IL警告使用
    #pragma warning disable
    。它会在构建时隐藏Roslyn分析器的警告,但IL链接器和AOT编译器仍会识别到该问题,代码会在裁剪/发布阶段失败。
  • 绝对不要使用
    [UnconditionalSuppressMessage]
    。它会让分析器和链接器都忽略该警告,意味着裁剪器无法验证安全性。构建时报错永远比隐藏问题、让它在运行时静默崩溃更可取。

💡 Preferred approaches

💡 推荐方案

  • Prefer
    [DynamicallyAccessedMembers]
    annotations to flow type information through the call chain.
  • Prefer refactoring to eliminate patterns that break annotation flow (e.g., boxing
    Type
    through
    object[]
    ).
  • Use
    [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.
  • 优先使用
    [DynamicallyAccessedMembers]
    注解在调用链中传递类型信息。
  • 优先通过重构消除破坏注解传递的模式(例如通过
    object[]
    装箱
    Type
    )。
  • 使用
    [RequiresUnreferencedCode]
    /
    [RequiresDynamicCode]
    /
    [RequiresAssemblyFiles]
    标记本质上与裁剪不兼容的方法,将要求传递给调用方。这会清晰地暴露问题而非隐藏——调用方必须显式确认该不兼容性。

Annotation flow is key

注解传递是核心

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.
裁剪器会通过赋值、参数传递和返回值追踪
[DynamicallyAccessedMembers]
注解。如果传递链路被破坏(例如将
Type
装箱为
object
、存储在无类型集合中、或通过接口强制转换),裁剪器会丢失追踪并发出警告。修复方案是保留传递链路,而非抑制警告。

Step-by-Step Procedure

分步操作流程

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
, or
grep
to 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.
不要预先探索代码库。 构建警告会明确告诉你哪些文件和行需要修改。遵循紧凑循环:构建→选中一个警告→打开对应文件的对应行→应用修复方案→重新构建。阅读或分析特定警告指向之外的源文件是无用功,还会导致超时。让编译器引导你操作。
❌ 构建前不要运行
find
ls
grep
来了解项目结构,不要阅读README、文档或架构文件。你的第一个操作应该是步骤1(启用AOT分析),然后构建。

Step 1: Enable AOT analysis in the .csproj

步骤1:在.csproj中启用AOT分析

Add
IsAotCompatible
. If the project doesn't exclusively target net8.0+, add a TFM condition (AOT analysis requires net8.0+):
xml
<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.
添加
IsAotCompatible
。如果项目不只是面向net8.0+,添加TFM条件(AOT分析需要net8.0+):
xml
<PropertyGroup>
  <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>
这会自动为兼容的TFM设置
EnableTrimAnalyzer=true
EnableAotAnalyzer=true
。对于多目标框架项目(例如
netstandard2.0;net8.0
),该条件可确保旧版TFM不会出现
NETSDK1210
警告。

Step 2: Build and collect warnings

步骤2:构建并收集警告

bash
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:
  • IL2070: Reflection call on a
    Type
    parameter missing
    [DynamicallyAccessedMembers]
  • IL2067: Passing an unannotated
    Type
    to a method expecting
    [DynamicallyAccessedMembers]
  • IL2072: Return value or extracted value missing annotation (often from unboxing)
  • IL2057:
    Type.GetType(string)
    with a non-constant argument
  • IL2026: Calling a method marked
    [RequiresUnreferencedCode]
  • IL2050: P/invoke method with COM marshalling parameters
  • IL2075: Return value flows into reflection without annotation
  • IL2091: Generic argument missing
    [DynamicallyAccessedMembers]
    required by constraint
  • IL3000:
    Assembly.Location
    returns empty string in single-file/AOT apps
  • IL3050: Calling a method marked
    [RequiresDynamicCode]
bash
dotnet build <project.csproj> -f <net8.0-or-later-tfm> --no-incremental 2>&1 | grep 'IL[0-9]\{4\}'
排序并去重。常见警告编码:
  • IL2070:缺少
    [DynamicallyAccessedMembers]
    Type
    参数上的反射调用
  • IL2067:将未注解的
    Type
    传递给需要
    [DynamicallyAccessedMembers]
    的方法
  • IL2072:返回值或提取的值缺少注解(通常来自拆箱)
  • IL2057:使用非常量参数调用
    Type.GetType(string)
  • IL2026:调用标记了
    [RequiresUnreferencedCode]
    的方法
  • IL2050:带有COM封送参数的P/invoke方法
  • IL2075:返回值未加注解就流入反射逻辑
  • IL2091:泛型参数缺少约束要求的
    [DynamicallyAccessedMembers]
  • IL3000:单文件/AOT应用中
    Assembly.Location
    返回空字符串
  • IL3050:调用标记了
    [RequiresDynamicCode]
    的方法

Step 3: Triage warnings by code (do NOT read every file)

步骤3:按编码分类警告(不要读取每个文件)

Group 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:
PatternTypical 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.
将步骤2得到的警告按警告编码分组并统计数量。先不要打开单个文件。 按数量找出排名前1-2的模式——这会决定你的修复策略:
模式典型修复方案
大量来自
JsonSerializer
的IL2026 + IL3050
直接使用策略C —— 创建
JsonSerializerContext
,然后批量更新所有调用点
Type
参数上的IL2070/IL2087
给最内层方法添加
[DynamicallyAccessedMembers]
,然后向外层传递
传递未注解
Type
导致的IL2067
在源头给参数添加注解
在大多数实际项目中,JsonSerializer导致的IL2026/IL3050占比最高。 除非警告分布明显显示其他问题,否则从策略C开始。批量修复JSON相关问题后,用策略A-B处理剩余警告。仅将策略D作为最后手段。

Step 4: Fix warnings iteratively (innermost first)

步骤4:迭代修复警告(从最内层开始)

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, replace
JsonSerializer.Serialize(writer, value)
with
JsonSerializer.Serialize(writer, value, MyProjectJsonContext.Default.TypeName)
and
JsonSerializer.Deserialize<T>(ref reader)
with
JsonSerializer.Deserialize(ref reader, MyProjectJsonContext.Default.TypeName)
. Only edit the JsonSerializer call sites.
最内层反射调用向外层处理。每次修复可能会给调用方带来新的警告。
始终以警告为导向。 针对每个警告,仅打开编译器报告的对应文件和行,识别模式,应用下方对应的修复方案,然后继续。不要扫描代码库查找类似模式,也不要尝试理解完整架构——修复编译器指出的问题,重新构建,让新的警告引导下一步修改。每修复小批量警告(5-10个),立即重新构建检查进度。
可用时使用子Agent。 如果你可以启动子Agent(例如通过
task
工具),并行调度多个子Agent同时编辑不同文件。让主循环专注于构建、解析警告和调度——将实际的文件编辑委托给子Agent。对于批量JSON更新,给每个子Agent分配5-10个文件一次性完成更新。经过2次构建-修复循环后,将所有剩余的文件编辑任务并行分配给子Agent——不要继续顺序修复文件。 示例:
更新以下文件以使用源生成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调用点。

Strategy A: Add
[DynamicallyAccessedMembers]
(preferred)

策略A:添加
[DynamicallyAccessedMembers]
(首选)

When a method uses reflection on a
Type
parameter, annotate the parameter to tell the trimmer what members are needed:
csharp
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.
当方法对
Type
参数使用反射时,给参数添加注解,告知裁剪器需要哪些成员:
csharp
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警告。

Strategy B: Refactor to preserve annotation flow

策略B:重构以保留注解传递

When annotation flow is broken by boxing (storing
Type
in
object
,
object[]
, or untyped collections), refactor to pass the
Type
directly:
csharp
// 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 parameter
  • Dictionary/List storage: Use a typed field with annotation instead
  • Interface indirection: Add annotation to the interface method's parameter
  • Property with boxing getter: Annotate the property's return type
当注解传递因装箱被破坏(将
Type
存储在
object
object[]
或无类型集合中),重构直接传递
Type
csharp
// 有问题: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
    提取为专属的带注解参数
  • 字典/列表存储:改用带注解的类型化字段
  • 接口间接调用:给接口方法的参数添加注解
  • 带装箱getter的属性:给属性的返回类型添加注解

Strategy C: Source-generated JSON serialization (batch fix)

策略C:源生成JSON序列化(批量修复)

When most warnings are IL2026/IL3050 from
JsonSerializer.Serialize
/
Deserialize
, this is a single mechanical fix applied in bulk:
  1. 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
    ).
  2. Create one
    JsonSerializerContext
    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.
csharp
[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 { }
  1. 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:
    bash
    # 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 use
    sed
    for C# code
    — generics like
    Deserialize<T>()
    have angle brackets and nested parentheses that sed will mangle.
  2. Build once to verify. Remaining warnings will be non-serialization issues — handle those with Strategies A–B or D.
当大多数警告是
JsonSerializer.Serialize
/
Deserialize
导致的IL2026/IL3050时,这是一个可批量应用的机械化修复方案:
  1. 收集受影响的类型 —— grep查找所有
    JsonSerializer.Serialize
    JsonSerializer.Deserialize
    调用点。提取要序列化的类型(
    Deserialize<T>
    中的
    <T>
    ,或
    Serialize
    中对象的运行时类型)。
  2. 创建一个
    JsonSerializerContext
    ,给每个找到的类型添加
    [JsonSerializable]
    跳过外部包的类型(例如
    Azure.Core
    ResponseError
    )——你不拥有的类型无法生成源生成代码。通过下方注意事项1单独处理外部类型。
csharp
[JsonSerializerContext]
[JsonSerializable(typeof(ManagedServiceIdentity))]
[JsonSerializable(typeof(SystemData))]
// ... 你拥有的每个类型对应一个属性
// 不要添加外部包的类型(例如ResponseError)
internal partial class MyProjectJsonContext : JsonSerializerContext { }
  1. 批量更新所有调用点 —— 不要逐个读取每个文件。机械化应用以下模式:
    • JsonSerializer.Serialize(obj)
      JsonSerializer.Serialize(obj, MyProjectJsonContext.Default.TypeName)
    • JsonSerializer.Deserialize<T>(json)
      JsonSerializer.Deserialize(json, MyProjectJsonContext.Default.TypeName)
    一次性查找并更新所有调用点:
    bash
    # 查找所有包含JsonSerializer调用的文件
    grep -rl 'JsonSerializer\.\(Serialize\|Deserialize\)' src/ --include='*.cs'
    然后使用顺序
    edit
    调用对每个匹配文件应用相同的转换。不要对C#代码使用
    sed
    —— 类似
    Deserialize<T>()
    的泛型带有尖括号和嵌套括号,sed会解析错误。
  2. 构建一次验证。剩余警告将是非序列化相关问题——用策略A-B或D处理。

Strategy D:
[RequiresUnreferencedCode]
(last resort)

策略D:
[RequiresUnreferencedCode]
(最后手段)

When a method fundamentally requires arbitrary reflection that cannot be statically described:
csharp
[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.
当方法本质上需要无法静态描述的任意反射时使用:
csharp
[RequiresUnreferencedCode("使用Assembly.Load按名称加载插件")]
public void LoadPlugin(string assemblyName) {
    var asm = Assembly.Load(assemblyName);
    // ...
}
这会传递给调用方——它们也必须用
[RequiresUnreferencedCode]
注解。谨慎使用;它会将整个调用链标记为裁剪不兼容。

Step 5: Rebuild and repeat

步骤5:重新构建并重复

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)
.
每修复小批量警告(5-10个)后,使用
--no-incremental
重新构建,检查是否有新警告。不要尝试在重新构建前修复所有警告——频繁构建可以尽早发现错误,暴露传递性警告。修复是连锁的——给内层方法添加注解可能会在其调用方中暴露警告。重复直到出现
0 Warning(s)

Step 6: Validate all TFMs

步骤6:验证所有TFM

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.)
bash
dotnet build <project.csproj>  # builds all TFMs
构建所有目标框架以确保:
  • net8.0+ TFM上0个IL警告
  • 无NETSDK1210警告
    IsAotCompatible
    条件会处理该问题)
  • 旧版TFM(netstandard2.0、net472等)上构建干净
bash
dotnet build <project.csproj>  # 构建所有TFM

Stop Signals

停止信号

  • Do not analyze more than 2-3 representative files per warning pattern. After identifying the fix for a pattern, apply it to all matching files without reading each one first.
  • Start fixing after one build. Do not do a second analysis pass — begin implementing fixes for the most common warning pattern immediately after Step 3 triage.
  • Stop after achieving 0 IL warnings for net8.0+ TFMs. Don't optimize or refactor already-clean annotations.
  • If a warning requires architectural refactoring beyond annotation flow fixes (e.g., replacing an entire serialization layer), document it and stop — don't rewrite large subsystems.
  • Limit to 3 build-fix iterations per warning. If annotation flow doesn't resolve it after 3 attempts, escalate to
    [RequiresUnreferencedCode]
    .
  • Don't chase warnings in third-party dependencies you can't modify. Note them and move on.
  • If the user asked a scoped question (e.g., "fix warnings in this file"), don't expand to the entire project.
  • 每种警告模式最多分析2-3个代表性文件。 确定某模式的修复方案后,直接应用到所有匹配文件,不要预先读取每个文件。
  • 一次构建后就开始修复。 不要进行第二次分析——步骤3分类后立即开始修复最常见的警告模式。
  • 针对net8.0+ TFM实现0个IL警告后停止。不要优化或重构已经干净的注解。
  • 如果某个警告需要架构重构超出注解传递修复的范围(例如替换整个序列化层),记录问题后停止——不要重写大型子系统。
  • 每个警告最多进行3次构建-修复迭代。如果3次尝试后注解传递仍无法解决该问题,升级使用
    [RequiresUnreferencedCode]
  • 不要处理你无法修改的第三方依赖中的警告。记录后继续。
  • 如果用户提出的是范围明确的问题(例如“修复该文件中的警告”),不要扩展到整个项目。

Polyfills for Older TFMs

旧版TFM的Polyfill

For multi-targeting projects that include netstandard2.0 or net472, you need polyfills for
DynamicallyAccessedMembersAttribute
and related types. See references/polyfills.md.
对于包含netstandard2.0或net472的多目标项目,你需要
DynamicallyAccessedMembersAttribute
及相关类型的polyfill。参见references/polyfills.md

Common Gotchas

常见注意事项

  1. External types without AOT-safe serialization: When a type comes from a dependency you can't modify (e.g.,
    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:
csharp
// 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.
  1. 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]
    .
  2. 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.
  1. 无AOT安全序列化的外部类型:当类型来自你无法修改的依赖(例如
    Azure.Core
    ResponseError
    )且没有源生成序列化器时,
    Options.GetConverter<T>()
    是基于反射的,会产生IL警告。首先检查该类型是否实现了
    IJsonModel<T>
    (Azure SDK中很常见)——如果是,完全绕过
    JsonSerializer
csharp
// 修改前(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>
,编写自定义
JsonConverter<T>
,使用手动
Utf8JsonReader
/
Utf8JsonWriter
逻辑,并通过上下文的
[JsonSourceGenerationOptions]
注册。
  1. 序列化库:大多数基于反射的序列化器(例如
    Newtonsoft.Json
    XmlSerializer
    )不兼容AOT。迁移到基于源生成的序列化器,例如搭配
    JsonSerializerContext
    System.Text.Json
    。如果无法迁移,给序列化调用点标记
    [RequiresUnreferencedCode]
  2. 共享项目 / projitems:当源代码通过
    <Import>
    在多个项目间共享时,给共享代码添加的注解会影响所有使用该代码的项目。验证所有使用者仍可正常构建。

References

参考资料

Checklist

检查清单

  • Added
    <IsAotCompatible>
    with TFM condition to .csproj
  • Built with AOT analyzers enabled (net8.0+ TFM)
  • Fixed all IL warnings via annotations or refactoring
  • No
    #pragma warning disable
    or
    [UnconditionalSuppressMessage]
    used for any IL warning
  • Polyfills present for older TFMs if needed
  • All target frameworks build with 0 warnings
  • Verified shared/linked source doesn't break sibling projects
  • 已在.csproj中添加带TFM条件的
    <IsAotCompatible>
  • 已启用AOT分析器构建(net8.0+ TFM)
  • 已通过注解或重构修复所有IL警告
  • 未对任何IL警告使用
    #pragma warning disable
    [UnconditionalSuppressMessage]
  • 如需支持旧版TFM已添加对应的polyfill
  • 所有目标框架构建均无警告
  • 已验证共享/链接源码不会破坏关联项目