.NET AOT 详解

ASP.NET教程 2025-08-24

目录

  • 简介
  • .NET 中主要的 AOT 形式
  • Mono AOT(Xamarin / Unity 场景)
  • AOT 在 .NET 中的演进与对比
  • 为什么要使用 AOT
  • 如何在项目中启用 AOT
  • Native AOT 示例
  • AOT 的优缺点及适用场景
  • 典型适用场景
  • 编写简单代码 Program.cs
  • 运行与验证
  • 分析产物体积
  • 如果使用了反射
  • 诊断和调试
  • rd.xml 配置文件
  • 常用配置项说明
  • .rd.xml 示例演示
  • 保留 JSON(或其他序列化)所需成员
  • 保留特定成员(Method / Field / Property)
  • 在项目中集成 .rd.xml
  • 编译与发布命令

简介

AOT(Ahead-Of-Time Compilation)是一种将代码直接编译为机器码的技术,与传统的 JIT(Just-In-Time Compilation)编译方式形成对比。在.NET中,AOT编译可以在应用发布时将 IL(中间语言)代码转换为平台特定的机器码,而不是在运行时进行 JIT编译。

与 JIT 的区别

JIT(即时编译)

  • 优点:灵活,运行时可以根据实际硬件做优化;对于不常用代码部分可延迟生成机器码,节省空间。
  • 缺点:应用启动时会有 JIT开销,若大量方法首次调用时需要编译,会影响首次执行性能(cold start);运行时动态编译也会增加 CPU占用。

AOT(预编译)

  • 优点:发布包中已包含机器码,运行时不再需要编译,启动速度更快;减少运行时依赖(部分场景下可做到无 .NET运行时依赖)。
  • 缺点:生成的二进制体积通常比仅 IL更大;缺少 JIT运行时才能做的某些动态优化;对反射、动态代码(如 System.Reflection.Emit、某些动态库调用)支持有限,需要额外配置。

.NET 中主要的 AOT 形式

ReadyToRun(R2R)

  • 原理:.NET Core 3.0+引入的预编译方案,通过 crossgen或 crossgen2工具,将 IL打包成一种混合格式:既包含 IL,也包含部分已编译的本机代码片段。运行时遇到已编译的本机代码就直接执行,未编译部分仍可 JIT。
  • 特点:
    • 部署包仍然包含 IL,因此仍需要 CLR支持执行 IL;
    • 与纯 JIT相比,能显著缩短冷启动时间;
    • 兼容性较好,不会因为反射动态调用导致编译失败;
    • 可通过项目文件中 PublishReadyToRun=true启用。

使用示例(.NET 6/7均适用)

PropertyGroup
  PublishReadyToRuntrue/PublishReadyToRun
  !-- 可以指定针对某一架构:anycpu、x64、arm64 等 --
  PublishReadyToRunUseCrossgen2true/PublishReadyToRunUseCrossgen2
/PropertyGroup

运行

dotnet publish -c Release -r win-x64

优缺点:

  • 优点:相对于纯 IL包体积增量较小;兼容性强;启动速度提升显著。
  • 缺点:仍需 CLR支持;对于某些存在大量泛型/反射调用的应用,如果 R2R编译时未覆盖,运行时会有 JIT编译;对发布包体积有一定增加。

Native AOT(以前称为 CoreRT、.NET Native)

  • 原理:自 .NET 7起,官方推出了基于 Native AOT 的技术分支,可以将应用编译为真正的本机可执行文件,省去了运行时(CoreCLR)依赖。Native AOT在编译阶段会对所有可达代码(包括泛型实例化、已知反射调用等)进行全局分析,并生成极致优化的机器码,同时将垃圾回收、类型元数据等必要组件整合进单一可执行文件或较少的 DLL。
  • 特点:
    • 最终输出为原生可执行文件,运行时无需安装 .NET运行时;
    • 启动速度和内存使用均优于 JIT或 R2R;
    • 可实现瘦二进制(使用修剪(trimming)技术去除未使用的程序集和类型);
    • 对反射、动态代码支持有限,需要手动配置 rd.xml或 TrimmerRootAssembly、DynamicDependency等显式保留信息;
    • 目前仅支持 控制台应用/单一可执行体,不支持 ASP.NET Core完整框架(仅支持最小化API)。

使用示例

PropertyGroup
  !-- 标记为可 AOT 发布 --
  PublishAottrue/PublishAot
  !-- 发布时去除不需要的依赖,减小体积 --
  PublishTrimmedtrue/PublishTrimmed
  !-- 指定运行时环境,比如 win-x64, linux-x64, linux-arm64 等 --
  RuntimeIdentifierwin-x64/RuntimeIdentifier
  !-- 若项目使用 WinForms/WPF,则需要设置此项为 false 或调试 --
  SelfContainedtrue/SelfContained
/PropertyGroup

运行

dotnet publish -c Release

完成后,会在 binReleasenet7.0win-x64publish目录下得到一个 .exe(或对应平台无后缀可执行文件)。

优缺点:

  • 优点:运行时零依赖、启动极快、内存占用低、体积可控(借助修剪);
  • 缺点:不支持某些 运行时动态 场景(如 Reflection.Emit、动态加载插件、ScriptEngine等);对反射访问的类型/成员必须在编译时预声明,否则会被修剪;调试体验不如 JIT(需要额外符号文件);生态兼容度仍在完善中。

Mono AOT(Xamarin / Unity 场景)

  • 原理:Mono提供的 AOT功能,可以在 iOS/macOS/Android等平台上将 IL预编译为机器码,避免目标平台禁止 JIT的限制(特别是在 iOS上)。
  • 特点:
    • 主要应用于移动端(Xamarin.iOS、Unity iOS构建等);
    • 编译过程会在打包时将 IL转为目标架构的本机代码;
    • 对大多数标准库和第三方库支持较好,但执行时会额外加载 Blitz或 Mono运行时支持(并非完全剥离)。

使用示例:

  • 在 Xamarin.iOS项目中,默认 Release模式下会启用 AOT;也可在项目属性中手动开启Enable LLVM和AOT Only(仅 AOT)。
  • 在 Unity构建 iOS时,也会默认将脚本代码以 AOT模式编译。

优缺点:

  • 优点:符合 iOS平台安全/性能需求;
  • 缺点:包体积增大;使用某些需要运行时生成 IL的库会失败。

AOT 在 .NET 中的演进与对比

特性 / 版本 .NET Core 3.x .NET 5/6 R2R .NET 7/8 Native AOT Mono AOT(Xamarin/Unity)
最终产物 包含 IL + 已编译部分类别的 .dll/.exe 纯本机可执行文件(或较少依赖文件) 本机代码 + Mono 运行时库
运行时依赖 依赖 CoreCLR 零依赖或极少依赖(视 SelfContained 而定) 依赖 Mono 运行时
启动速度 明显优于纯 JIT,但仍有部分 JIT 最优;几乎无需运行期编译开销 较优于 JIT,但受限于 Mono 负载
支持的应用类型 全部(包括 ASP.NET Core) 控制台、工具类应用;对 ASP.NET Core 支持有限 移动端(iOS/Android)、Unity 游戏
反射 / 动态代码 全量支持;运行时仍可 JIT 需手动指定反射保留;不支持动态生成 IL 大部分反射可用,但动态 IL 支持受限
发布包体积 较 IL + JIT 稍大 可经修剪后显著减小;自行选择不同运行时 通常最大,因为包含 Mono 运行时和 AOT 文件

为什么要使用 AOT

加快冷启动

  • 对于启动时间敏感的应用(如命令行工具、微服务、Serverless函数、IoT设备、移动端应用等),AOT能显著减少启动时等待 JIT编译的开销。

减少运行时依赖

  • Native AOT可将运行时(CoreCLR)与垃圾回收等程序集成到一个可执行文件里,实现零依赖部署。对于需要极简体积或目标环境不允许安装 .NET运行时的场景(比如无管理员权限的服务器、Linux发行版中未安装 .NET、Docker镜像瘦身需求),非常有帮助。

提高性能可预测性

  • 因为所有代码都在发布前编译完成,运行时不会有突然的 JIT阶段,尤其在高并发场景下,避免了突发的编译延迟或 CPU峰值。
  • 对于资源受限的环境(嵌入式、边缘计算设备),可减少 JIT引发的内存和 CPU瞬时占用。

安全性 / 平台限制

  • 某些平台(如 iOS)不允许运行时生成机器码(即禁止 JIT),必须使用 AOT。Mono AOT以及 Xamarin都基于此需求在 iOS平台默认强制 AOT。

二进制可移植性

  • 在 Native AOT下,可将生成的可执行文件拷贝至目标环境直接运行,无需在目标环境重新编译,提升交付效率。 AOT 实现的关键流程

扫描可达程序集

  • 编译器(IL to object)需要先扫描所有引用的程序集,收集根节点(Main方法、动态库加载点、反射需求等)。
  • 通过 IL Linker(修剪器)算法,对树状可达性做静态分析。对于未标记为可达的代码进行剔除,从而减小体积。

生成本机代码

  • 将 IL转换为中间的中间表示(如 RyuJIT IR或 LLVM IR),并通过平台本地编译器(例如 MSVC、LLVM)优化后生成对应体系结构的机器码。
  • 同时将 GC、类型元数据、异常处理表等运行时信息打包到可执行文件中。

处理反射 / 动态需求

  • 因为编译时无法窥见运行时可能使用的反射类型,需要开发者通过属性或 XML(.rd.xml)声明需要保留的类型/程序集/成员,否则编译器会在做修剪时误删这些代码。

.NET 7+用 DynamicDependencyAttribute、PreserveDependency等方式告知编译器保留反射访问所需类型。

生产最终可执行文件

  • 静态地将机器码、元数据、运行时库等整合成一个单一文件,或者如 Linux下分为可执行 + 一些 .so文件。
  • 通过 dotnet publish -c Release -r RID /p:PublishAot=true完成。

如何在项目中启用 AOT

ReadyToRun(R2R)示例

在 .csproj中添加属性:

PropertyGroup
  !-- 启用 ReadyToRun 预编译 --
  PublishReadyToRuntrue/PublishReadyToRun
  !-- 使用 CrossGen2 进行预编译(建议在 .NET 6+ 使用) --
  PublishReadyToRunUseCrossgen2true/PublishReadyToRunUseCrossgen2
  !-- 可选:指定是否在发布时生成非托管符号文件 --
  PublishReadyToRunEmitSymbolstrue/PublishReadyToRunEmitSymbols
  !-- 指定具体目标平台 --
  RuntimeIdentifierwin-x64/RuntimeIdentifier
/PropertyGroup

然后执行:

dotnet publish -c Release -r win-x64 --self-contained false

Native AOT 示例

在 .csproj中添加:

PropertyGroup
  !-- 开启 Native AOT 发布 --
  PublishAottrue/PublishAot
  !-- 开启修剪,减小体积 --
  PublishTrimmedtrue/PublishTrimmed
  !-- 例如发布为控制台应用,可选改为 false 如果涉及 WinForms/WPF --
  SelfContainedtrue/SelfContained
  !-- 目标运行时标识符 --
  RuntimeIdentifierwin-x64/RuntimeIdentifier
  !-- 可选:发布时同时生成调试符号 --
  DebugTypeembedded/DebugType
  !-- 如需使用单文件发布 --
  PublishSingleFiletrue/PublishSingleFile
  !-- 可选:剔除 Diagnostics 诊断支持以进一步减小体积 --
  InvariantGlobalizationtrue/InvariantGlobalization
/PropertyGroup

然后运行:

dotnet publish -c Release

注意:

  • 如果项目中使用到了反射(例如 Activator.CreateInstance、Type.GetType、JsonSerializer等),编译时需要添加对应的保留配置,否则会出现无法找到类型或成员的运行时异常。
  • 对于依赖第三方 NuGet包且该包使用了动态特性,也需要检查是否 AOT兼容;部分库需要手动编写 TrimmerRootAssembly或自定义 rd.xml。

AOT 的优缺点及适用场景

优点

极快启动:

  • 省去运行时 JIT阶段,首屏响应或启动速度几乎与本机程序无差异。

部署简单:

  • Native AOT模式下可执行文件自包含所有依赖,无需目标机器预先安装 .NET运行时。

更小内存占用峰值:

  • 通过修剪技术剔除未使用的代码和依赖,运行时加载更轻量。

可预测走向发布:

  • 编译时已做全部优化与检查,减少运行期编译出错或动态缺失依赖的问题。

符合某些平台限制:

  • 比如在 iOS平台禁止 JIT,通过 Mono AOT或 Xamarin iOS编译可满足 Apple审核要求。

缺点

体积膨胀 vs 兼容性:

ReadyToRun相对于纯 IL包增量并不大,但 Native AOT若不精心修剪,体积可能与完整运行时相当;

某些第三方库对 AOT支持不好,可能需要额外适配。

有限的运行时代码生成:

无法做 Reflection.Emit、动态生成表达式树等,需要在编译期预先声明;

运行时使用 System.Text.Json、Newtonsoft.Json等反射型序列化/反序列化时,需手动配置 JsonSerializerContext或显式注册要序列化的类型。

调试和诊断不便:

Native AOT下 StackTrace可能缺少符号映射;

如出现 CPU性能或内存泄漏问题,无法借助 JIT时代的动调。

不适合大型动态场景:

  • 如果项目本身依赖插件热加载、脚本引擎、或者大量运行时元编程,就不适合 Native AOT。

典型适用场景

命令行工具(CLI)

  • 比如一些 Git扩展工具、DevOps脚本工具、跨平台部署时,Native AOT可做到零依赖、秒级启动。

微服务/Serverless 函数

  • 在容器或云函数中,冷启动时间至关重要;使用 AOT或 R2R可降低冷启动延迟。

桌面/移动端轻量应用

  • 某些场景下需要小体积、无运行时依赖,或目标平台禁止 JIT,可考虑 AOT。

IoT/嵌入式设备

  • 在资源受限的硬件上,减少运行时占用,提升响应速度。 创建一个 Native AOT 控制台应用

创建项目

dotnet new console -n AotDemo
cd AotDemo

修改项目文件 AotDemo.csproj

Project Sdk="Microsoft.NET.Sdk"
  PropertyGroup
    OutputTypeExe/OutputType
    TargetFrameworknet7.0/TargetFramework
    !-- 开启 Native AOT --
    PublishAottrue/PublishAot
    !-- 发布时进行修剪 --
    PublishTrimmedtrue/PublishTrimmed
    !-- 将所有依赖打包到单个可执行文件里 --
    PublishSingleFiletrue/PublishSingleFile
    !-- 自包含部署(包含运行时) --
    SelfContainedtrue/SelfContained
    !-- 运行时标识符,根据目标平台调整 --
    RuntimeIdentifierwin-x64/RuntimeIdentifier
    !-- 便于调试,可以嵌入 PDB 符号 --
    DebugTypeembedded/DebugType
  /PropertyGroup
/Project

编写简单代码 Program.cs

using System;
using System.Reflection;
namespace AotDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello Native AOT World!");
            // 示例:试图使用反射读取自身类型
            var type = typeof(Program);
            Console.WriteLine($"当前类型:{type.FullName}");
        }
    }
}

编译并发布

在项目根目录执行:

dotnet publish -c Release

发布完成后,打开 binReleasenet7.0win-x64publish,可以看到 AotDemo.exe或 AotDemo

运行与验证

直接双击或命令行执行 AotDemo.exe或 ./AotDemo,输出:

Hello Native AOT World!
当前类型:AotDemo.Program

分析产物体积

  • 若没有配置修剪(PublishTrimmedtrue/PublishTrimmed),可执行文件体积约在 3050MB 左右。
  • 启用修剪后,一般可以减小到 1020MB(视代码复杂度及所引用包而定)。

如果使用了反射

  • 在 Native AOT中直接调用 typeof(Program)读取自身类型是可以的,因为编译器会保留 Program类;
  • 如果反射调用一个仅在运行期才可确定的类型(例如字符串拼接得到的类型名称),编译时无法知道,就需要在项目里添加 rd.xml或使用 DynamicDependency属性显式声明保留该类型。

诊断和调试

  • 对于 Native AOT,调试体验不如 JIT平滑,特别是断点、StackTrace、Debug.Assert之类需要符号的场景。
  • 建议:仅在开发阶段默认关闭 AOT,保留 JIT类库的常规调试;发布前再切换至 AOT。

rd.xml 配置文件

作用

在 .NET Native AOT、ReadyToRun+ 修剪(ILLinker)等场景中,编译器或 IL链接器会在发布阶段对中间语言(IL)进行分析与修剪(Trim),剔除未被静态调用或引用的类型、成员与元数据,以减小输出体积、提升启动性能。然而,反射(Reflection)是一种运行时特性:代码中的某些类型、方法、属性等只有在运行阶段通过反射动态访问,编译时并不可见。若不做额外保留,ILLinker在静态分析时会误判这些成员为不可达,进而被剔除,导致在运行时使用诸如 Activator.CreateInstance、Type.GetType、序列化/反序列化、ORM映射等场景抛出 找不到类型/成员 的异常。

.rd.xml文件是 .NET Native与 ILLink场景下的保留指令描述文件(runtime directives XML)。通过在其中显式声明要保留的程序集/类型/成员/属性,可以避免它们在发布构建时被错误剔除,从而保证反射相关逻辑在运行时正常工作。

基本结构

一个典型的 .rd.xml(Runtime Directives XML)文件的根节点为 Directives,其下通常有一个 Application或 Library节点,后者根据项目类型(控制台应用、类库等)有所不同。

?xml version="1.0" encoding="utf-8"?
Directives xmlns="http://schemas.mi*cros*o*ft.com/netcore/2013/01/metadata"
  !-- 
    Application: 用于标记应用程序可达的根节点; 
    Library: 用于类库时的配置(一般也可放在 Application 节点中)。
  --
  Application
    !-- 在这里声明要保留的程序集、类型、成员等 --
  /Application
/Directives

其中常用的子节点包括:

  • Assembly Name=... [Dynamic=...] [Serialize=...]:标记要保留的程序集,以及对该程序集下类型的保留策略。
  • Type Name=... [Dynamic=...] [Serialize=...]:在某个 Assembly内部,用于配置要保留的类型(类、接口、结构体、枚举等)。常见的属性:
    • Dynamic=Required All:保留该类型的所有成员(字段、属性、方法、事件等)以满足动态访问。
    • Serialize=Required Public:仅保留该类型公共可序列化成员,供 JSON/XML 序列化时使用。
  • Method Name=...、Field Name=...、Property Name=...等子节点:进一步精细到单个成员级别的保留配置。

常用配置项说明

在 Assembly和 Type节点上,常见的属性及含义如下:

  • Dynamic=Required All / Required Public / Required PublicAndCritical 等
    • Required All:保留该节点下所有成员(字段、属性、方法、事件等,无论是否为 public或 private),用于某些场景需要完全动态访问。
    • Required Public:仅保留公共(public)成员。
    • Required PublicAndCritical:保留公共成员以及具有安全 Critical特性。
  • Serialize=Required Public / Required All
    • 主要用于 JSON/XML序列化场景:保留类型的公有字段与属性,以便在运行时的序列化/反序列化机制(如 System.Text.Json或 XmlSerializer)能够正常工作。
    • 与 Dynamic同时存在时,序列化场景保留的成员更加精准(避免把每个私有成员都打包进二进制)。
  • Collections
    • 如果类型中有对泛型集合(ListT)的反射访问(例如 Activator.CreateInstance(typeof(List).MakeGenericType(...))),需要通过 GenericInstantiation子节点显式声明泛型实例化需求。
  • Version / Culture / PublicKeyToken
    • Assembly Name=Name Version=1.0.0.0 Culture=neutral PublicKeyToken=abcdef1234567890
    • 当引用了特定强命名程序集中类型,需要写明版本号与公钥令牌,才能准确匹配。若不写 Version/Culture/PublicKeyToken,ILLink会尝试做宽松匹配(仅匹配 Name)。

.rd.xml 示例演示

保留整个程序集(全部类型和成员)

假设项目中有一个 MyApp.Core.dll,并且在运行时会通过反射访问其中的所有类型(如插件、动态加载等)。若要保留 MyApp.Core程序集中所有内容,可以这样写:

?xml version="1.0" encoding="utf-8"?
Directives xmlns="http://schemas.mi*cros*o*ft.com/netcore/2013/01/metadata"
  Application
    !-- 忽略版本号、Culture、PublicKeyToken,以宽松方式匹配 MyApp.Core --
    Assembly Name="MyApp.Core" Dynamic="Required All" Serialize="Required All" /
  /Application
/Directives

保留特定类型(及其成员)

若只希望针对某个类型(如 MyApp.Core.Services.PluginLoader)进行反射保留:

Assembly Name="MyApp.Core"
  Type Name="MyApp.Core.Models.Entity`1" Dynamic="Required All"
    !-- 指定泛型实例化需求 --
    GenericInstantiation
      TypeNameMyApp.Core.Models.Entity`1[[MyApp.Core.Models.Customer, MyApp.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]/TypeName
    /GenericInstantiation
  /Type
/Assembly

若该类型是泛型类型,例如 MyApp.Core.Models.EntityT,且会在运行时实例化某个具体泛型(如 EntityCustomer)进行反射构造,需要这样指定:

Assembly Name="MyApp.Core"
  Type Name="MyApp.Core.Models.Entity`1" Dynamic="Required All"
    !-- 指定泛型实例化需求 --
    GenericInstantiation
      TypeNameMyApp.Core.Models.Entity`1[[MyApp.Core.Models.Customer, MyApp.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]/TypeName
    /GenericInstantiation
  /Type
/Assembly
  • 其中反引号后 1 表示一参泛型;
  • GenericInstantiation内指定了要实例化的具体泛型参数类型,必须给出该类型的程序集、版本、Culture、PublicKeyToken 等完整信息;否则无法被正确保留。

保留 JSON(或其他序列化)所需成员

在使用 System.Text.Json的源生成(source generator)方式时,可以通过 [JsonSerializable]等特性生成上下文,通常无需 .rd.xml。但如果使用反射式序列化(如 Newtonsoft.Json的默认行为),则需要保留类型的公共 getter/setter属性

namespace MyApp.Core.Models
{
    public class Customer
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        private DateTime Birthday { get; set; }
        public string SecretCode { get; private set; }
    }
}

若要保证 JSON序列化能访问到 Id、Name、SecretCode,可以这样配置:

Directives xmlns="http://schemas.mi*cros*o*ft.com/netcore/2013/01/metadata"
  Application
    Assembly Name="MyApp.Core"
      !-- 
        保留 Customer 类型的公有属性和字段 
        Serialize="Required Public" 表示仅保留 public 字段/属性 
      --
      Type Name="MyApp.Core.Models.Customer" Dynamic="Required Public" Serialize="Required Public" /
    /Assembly
  /Application
/Directives

这里 Dynamic=Required Public也会保留公有方法,若无需公有方法也可只用 Serialize=Required Public。如果只想保留序列化场景的 public属性,把 Dynamic去掉或设为更窄范围也可行。

保留特定成员(Method / Field / Property)

有时只需保留某个类型下的某个方法或字段,而不是整类型

Directives xmlns="http://schemas.mi*cros*o*ft.com/netcore/2013/01/metadata"
  Application
    Assembly Name="MyApp.Core"
      Type Name="MyApp.Core.Utils.Helper"
        !-- 仅保留名为 DoWork 的公有实例方法 --
        Method Name="DoWork" Dynamic="Required Public" /
        !-- 仅保留名为 _secretKey 的私有字段 --
        Field Name="_secretKey" Dynamic="Required All" /
        !-- 仅保留 Id 属性的 Getter / Setter --
        Property Name="Id" Dynamic="Required Public" /
      /Type
    /Assembly
  /Application
/Directives
  • Method、Field、Property节点均需要写上 Name;
  • Dynamic值可针对该节点保留范围进行调整。

在项目中集成 .rd.xml

将 .rd.xml文件放到项目根目录并在 .csproj中进行声明,确保编译时能被识别并生效。

在项目根目录(与 .csproj同级)放置文件,命名为 rd.xml,在 .csproj中引用 .rd.xml

PropertyGroup
  !-- 启用 Native AOT 发布 --
  PublishAottrue/PublishAot
  !-- 启用修剪 --
  PublishTrimmedtrue/PublishTrimmed
  !-- 标记要使用 rd.xml 作为保留指令 --
  TrimmerDefaultActionlink/TrimmerDefaultAction
  TrimmerRootDescriptorFilesrd.xml/TrimmerRootDescriptorFiles
  !-- 其他 AOT 相关配置 --
  RuntimeIdentifierwin-x64/RuntimeIdentifier
  PublishSingleFiletrue/PublishSingleFile
  SelfContainedtrue/SelfContained
/PropertyGroup
  • TrimmerDefaultAction:
    • link表示默认修剪所有未被标记为保留的代码;
    • copy表示复制所有引用的程序集但不修剪(相当于 R2R模式);
  • TrimmerRootDescriptorFiles:指定一个或多个 .rd.xml文件路径,多个文件以分号分隔。这里使用相对路径 rd.xml。

编译与发布命令

dotnet publish -c Release

编译器会自动读取 rd.xml,根据其中配置的保留指令对代码进行修剪,确保运行时反射需求的类型与成员被正确保留。

调试与验证

  • 启用链接器诊断日志

在 .csproj中添加或在命令行指定:

PropertyGroup
  !-- 打印链接器日志到指定文件 --
  TrimmerLogFiletrim-log.xml/TrimmerLogFile
  !-- 显示链接器分析的详细级别(可选:Skip、Silent、Verbose、diagnostic) --
  TrimmerLogLeveldiagnostic/TrimmerLogLevel
/PropertyGroup

发布后会在输出目录生成 trim-log.xml,打开后查找是否有某个类型/成员被修剪或被保留的记录。诊断级别输出会非常详细,便于查找保留是否生效,或哪些类型因遗漏而被剔除。

  • 运行时测试反射调用
    • 在发布后拷贝到干净环境,手动调用关键反射逻辑,验证是否抛出 TypeLoadException、MissingMethodException等。
    • 如果依旧报错,查看 trim-log.xml中对应类型/成员是否被剔除,若剔除则需要在 rd.xml做进一步保留。
  • 使用 ILSpy / dotnet-ildasm工具检查输出
    • 可对生成后的可执行文件或 .dll在反编译工具(ILSpy、dnSpy)中查看某些类型是否还存在。
    • 或者用 dotnet-ildasm导出元数据,再搜索对应类型/成员名。

到此这篇关于.NETAOT详解的文章就介绍到这了,更多相关.NETAOT内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

您可能感兴趣的文章:
  • .NET AOT 详解
  • .NETNativeAOT用法指南
  • .NET9 AOT部署方案详解
  • .NET 7 AOT 的使用及 .NET 与 Go 互相调用的过程
  • .NET Core单文件发布静态编译AOT CoreRT的方法详解