.NET AOT 详解

来源:这里教程网 时间:2026-02-21 14:40:19 作者:
简介.NET 中主要的 AOT 形式Mono AOT(Xamarin / Unity 场景)AOT 在 .NET 中的演进与对比为什么要使用 AOT如何在项目中启用 AOTNative 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+ 引入的预编译方案,通过 crossgencrossgen2 工具,将 IL 打包成一种混合格式:既包含 IL,也包含部分已编译的本机代码片段。运行时遇到已编译的本机代码就直接执行,未编译部分仍可 JIT特点:部署包仍然包含 IL,因此仍需要 CLR 支持执行 IL与纯 JIT 相比,能显著缩短冷启动时间;兼容性较好,不会因为反射动态调用导致编译失败;可通过项目文件中 PublishReadyToRun=true 启用。

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

<PropertyGroup>
  <PublishReadyToRun>true</PublishReadyToRun>
  <!-- 可以指定针对某一架构:anycpu、x64、arm64 等 -->
  <PublishReadyToRunUseCrossgen2>true</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 运行时;启动速度和内存使用均优于 JITR2R可实现瘦二进制(使用“修剪”(trimming)技术去除未使用的程序集和类型);对反射、动态代码支持有限,需要手动配置 rd.xmlTrimmerRootAssembly、DynamicDependency 等显式保留信息;目前仅支持 控制台应用/单一可执行体,不支持 ASP.NET Core 完整框架(仅支持最小化API)。

使用示例

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

运行

dotnet publish -c Release

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

优缺点:

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

Mono AOT(Xamarin / Unity 场景)

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

使用示例:

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 AOTMono 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),必须使用 AOTMono AOT 以及 Xamarin 都基于此需求在 iOS 平台默认强制 AOT

二进制可移植性

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

扫描可达程序集

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

生成本机代码

IL 转换为中间的“中间表示”(如 RyuJIT IRLLVM 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 预编译 -->
  <PublishReadyToRun>true</PublishReadyToRun>
  <!-- 使用 CrossGen2 进行预编译(建议在 .NET 6+ 使用) -->
  <PublishReadyToRunUseCrossgen2>true</PublishReadyToRunUseCrossgen2>
  <!-- 可选:指定是否在发布时生成非托管符号文件 -->
  <PublishReadyToRunEmitSymbols>true</PublishReadyToRunEmitSymbols>
  <!-- 指定具体目标平台 -->
  <RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>

然后执行:

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

Native AOT 示例

.csproj 中添加:

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

然后运行:

dotnet publish -c Release

注意:

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

AOT 的优缺点及适用场景

优点

极快启动:

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

部署简单:

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

更小内存占用峰值:

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

可预测走向发布:

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

符合某些平台限制:

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

缺点

体积膨胀 vs 兼容性:

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

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

有限的运行时代码生成:

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

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

调试和诊断不便:

Native AOTStackTrace 可能缺少符号映射;

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

不适合大型动态场景:

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

典型适用场景

命令行工具(CLI)

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

微服务/Serverless 函数

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

桌面/移动端轻量应用

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

IoT/嵌入式设备

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

创建项目

dotnet new console -n AotDemo
cd AotDemo

修改项目文件 AotDemo.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <!-- 开启 Native AOT -->
    <PublishAot>true</PublishAot>
    <!-- 发布时进行修剪 -->
    <PublishTrimmed>true</PublishTrimmed>
    <!-- 将所有依赖打包到单个可执行文件里 -->
    <PublishSingleFile>true</PublishSingleFile>
    <!-- 自包含部署(包含运行时) -->
    <SelfContained>true</SelfContained>
    <!-- 运行时标识符,根据目标平台调整 -->
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <!-- 便于调试,可以嵌入 PDB 符号 -->
    <DebugType>embedded</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

发布完成后,打开 bin\Release\net7.0\win-x64\publish\,可以看到 AotDemo.exeAotDemo

运行与验证

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

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

分析产物体积

若没有配置修剪(<PublishTrimmed>true</PublishTrimmed>),可执行文件体积约在 30–50MB 左右。启用修剪后,一般可以减小到 10–20MB(视代码复杂度及所引用包而定)。

如果使用了反射

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 NativeILLink 场景下的“保留指令”描述文件(runtime directives XML)。通过在其中显式声明“要保留的程序集/类型/成员/属性”,可以避免它们在发布构建时被错误剔除,从而保证反射相关逻辑在运行时正常工作。

基本结构

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

<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.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:保留该节点下所有成员(字段、属性、方法、事件等,无论是否为 publicprivate),用于某些场景需要完全动态访问。Required Public:仅保留公共(public)成员。Required PublicAndCritical:保留公共成员以及具有安全 Critical 特性。Serialize=“Required Public” / “Required All”主要用于 JSON/XML 序列化场景:保留类型的公有字段与属性,以便在运行时的序列化/反序列化机制(如 System.Text.JsonXmlSerializer)能够正常工作。Dynamic 同时存在时,序列化场景保留的成员更加精准(避免把每个私有成员都打包进二进制)。Collections如果类型中有对泛型集合(List<T>)的反射访问(例如 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.microsoft.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>
      <TypeName>MyApp.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.Entity<T>,且会在运行时实例化某个具体泛型(如 Entity<Customer>)进行反射构造,需要这样指定:

<Assembly Name="MyApp.Core">
  <Type Name="MyApp.Core.Models.Entity`1" Dynamic="Required All">
    <!-- 指定泛型实例化需求 -->
    <GenericInstantiation>
      <TypeName>MyApp.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.microsoft.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.microsoft.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 发布 -->
  <PublishAot>true</PublishAot>
  <!-- 启用修剪 -->
  <PublishTrimmed>true</PublishTrimmed>
  <!-- 标记要使用 rd.xml 作为保留指令 -->
  <TrimmerDefaultAction>link</TrimmerDefaultAction>
  <TrimmerRootDescriptorFiles>rd.xml</TrimmerRootDescriptorFiles>
  <!-- 其他 AOT 相关配置 -->
  <RuntimeIdentifier>win-x64</RuntimeIdentifier>
  <PublishSingleFile>true</PublishSingleFile>
  <SelfContained>true</SelfContained>
</PropertyGroup>
TrimmerDefaultActionlink 表示默认修剪所有未被标记为保留的代码;copy 表示复制所有引用的程序集但不修剪(相当于 R2R 模式);TrimmerRootDescriptorFiles:指定一个或多个 .rd.xml 文件路径,多个文件以分号分隔。这里使用相对路径 rd.xml

编译与发布命令

dotnet publish -c Release

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

调试与验证

启用链接器诊断日志

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

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

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

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

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

相关推荐