.NET?AOT?詳解
簡(jiǎn)介
AOT(Ahead-Of-Time Compilation)是一種將代碼直接編譯為機(jī)器碼的技術(shù),與傳統(tǒng)的 JIT(Just-In-Time Compilation)編譯方式形成對(duì)比。在.NET 中,AOT 編譯可以在應(yīng)用發(fā)布時(shí)將 IL(中間語(yǔ)言)代碼轉(zhuǎn)換為平臺(tái)特定的機(jī)器碼,而不是在運(yùn)行時(shí)進(jìn)行 JIT 編譯。
與 JIT 的區(qū)別
JIT(即時(shí)編譯)
- 優(yōu)點(diǎn):靈活,運(yùn)行時(shí)可以根據(jù)實(shí)際硬件做優(yōu)化;對(duì)于不常用代碼部分可延遲生成機(jī)器碼,節(jié)省空間。
- 缺點(diǎn):應(yīng)用啟動(dòng)時(shí)會(huì)有
JIT開(kāi)銷(xiāo),若大量方法首次調(diào)用時(shí)需要編譯,會(huì)影響“首次執(zhí)行性能”(cold start);運(yùn)行時(shí)動(dòng)態(tài)編譯也會(huì)增加CPU占用。
AOT(預(yù)編譯)
- 優(yōu)點(diǎn):發(fā)布包中已包含機(jī)器碼,運(yùn)行時(shí)不再需要編譯,啟動(dòng)速度更快;減少運(yùn)行時(shí)依賴(lài)(部分場(chǎng)景下可做到無(wú)
.NET運(yùn)行時(shí)依賴(lài))。 - 缺點(diǎn):生成的二進(jìn)制體積通常比僅
IL更大;缺少JIT運(yùn)行時(shí)才能做的某些動(dòng)態(tài)優(yōu)化;對(duì)反射、動(dòng)態(tài)代碼(如System.Reflection.Emit、某些動(dòng)態(tài)庫(kù)調(diào)用)支持有限,需要額外配置。
.NET 中主要的 AOT 形式
ReadyToRun(R2R)
- 原理:
.NET Core 3.0+引入的預(yù)編譯方案,通過(guò)crossgen或crossgen2工具,將IL打包成一種混合格式:既包含IL,也包含部分已編譯的本機(jī)代碼片段。運(yùn)行時(shí)遇到已編譯的本機(jī)代碼就直接執(zhí)行,未編譯部分仍可JIT。 - 特點(diǎn):
- 部署包仍然包含
IL,因此仍需要CLR支持執(zhí)行IL; - 與純
JIT相比,能顯著縮短冷啟動(dòng)時(shí)間; - 兼容性較好,不會(huì)因?yàn)榉瓷鋭?dòng)態(tài)調(diào)用導(dǎo)致編譯失敗;
- 可通過(guò)項(xiàng)目文件中
PublishReadyToRun=true啟用。
- 部署包仍然包含
使用示例(.NET 6/7 均適用)
<PropertyGroup> <PublishReadyToRun>true</PublishReadyToRun> <!-- 可以指定針對(duì)某一架構(gòu):anycpu、x64、arm64 等 --> <PublishReadyToRunUseCrossgen2>true</PublishReadyToRunUseCrossgen2> </PropertyGroup>
運(yùn)行
dotnet publish -c Release -r win-x64
優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):相對(duì)于純
IL包體積增量較?。患嫒菪詮?qiáng);啟動(dòng)速度提升顯著。 - 缺點(diǎn):仍需
CLR支持;對(duì)于某些存在大量泛型/反射調(diào)用的應(yīng)用,如果R2R編譯時(shí)未覆蓋,運(yùn)行時(shí)會(huì)有JIT編譯;對(duì)發(fā)布包體積有一定增加。
Native AOT(以前稱(chēng)為 CoreRT、.NET Native)
- 原理:自
.NET 7起,官方推出了基于 “Native AOT” 的技術(shù)分支,可以將應(yīng)用編譯為真正的本機(jī)可執(zhí)行文件,省去了運(yùn)行時(shí)(CoreCLR)依賴(lài)。Native AOT在編譯階段會(huì)對(duì)所有可達(dá)代碼(包括泛型實(shí)例化、已知反射調(diào)用等)進(jìn)行全局分析,并生成極致優(yōu)化的機(jī)器碼,同時(shí)將垃圾回收、類(lèi)型元數(shù)據(jù)等必要組件整合進(jìn)單一可執(zhí)行文件或較少的DLL。 - 特點(diǎn):
- 最終輸出為原生可執(zhí)行文件,運(yùn)行時(shí)無(wú)需安裝
.NET運(yùn)行時(shí); - 啟動(dòng)速度和內(nèi)存使用均優(yōu)于
JIT或R2R; - 可實(shí)現(xiàn)瘦二進(jìn)制(使用“修剪”(
trimming)技術(shù)去除未使用的程序集和類(lèi)型); - 對(duì)反射、動(dòng)態(tài)代碼支持有限,需要手動(dòng)配置
rd.xml或TrimmerRootAssembly、DynamicDependency等顯式保留信息; - 目前僅支持 控制臺(tái)應(yīng)用/單一可執(zhí)行體,不支持
ASP.NET Core完整框架(僅支持最小化API)。
- 最終輸出為原生可執(zhí)行文件,運(yùn)行時(shí)無(wú)需安裝
使用示例
<PropertyGroup> <!-- 標(biāo)記為可 AOT 發(fā)布 --> <PublishAot>true</PublishAot> <!-- 發(fā)布時(shí)去除不需要的依賴(lài),減小體積 --> <PublishTrimmed>true</PublishTrimmed> <!-- 指定運(yùn)行時(shí)環(huán)境,比如 win-x64, linux-x64, linux-arm64 等 --> <RuntimeIdentifier>win-x64</RuntimeIdentifier> <!-- 若項(xiàng)目使用 WinForms/WPF,則需要設(shè)置此項(xiàng)為 false 或調(diào)試 --> <SelfContained>true</SelfContained> </PropertyGroup>
運(yùn)行
dotnet publish -c Release
完成后,會(huì)在 bin\Release\net7.0\win-x64\publish\ 目錄下得到一個(gè) .exe(或?qū)?yīng)平臺(tái)無(wú)后綴可執(zhí)行文件)。
優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):運(yùn)行時(shí)零依賴(lài)、啟動(dòng)極快、內(nèi)存占用低、體積可控(借助修剪);
- 缺點(diǎn):不支持某些 “運(yùn)行時(shí)動(dòng)態(tài)” 場(chǎng)景(如
Reflection.Emit、動(dòng)態(tài)加載插件、ScriptEngine等);對(duì)反射訪問(wèn)的類(lèi)型/成員必須在編譯時(shí)預(yù)聲明,否則會(huì)被修剪;調(diào)試體驗(yàn)不如JIT(需要額外符號(hào)文件);生態(tài)兼容度仍在完善中。
Mono AOT(Xamarin / Unity 場(chǎng)景)
- 原理:
Mono提供的AOT功能,可以在iOS/macOS/Android等平臺(tái)上將IL預(yù)編譯為機(jī)器碼,避免目標(biāo)平臺(tái)禁止JIT的限制(特別是在iOS上)。 - 特點(diǎn):
- 主要應(yīng)用于移動(dòng)端(
Xamarin.iOS、Unity iOS構(gòu)建等); - 編譯過(guò)程會(huì)在打包時(shí)將
IL轉(zhuǎn)為目標(biāo)架構(gòu)的本機(jī)代碼; - 對(duì)大多數(shù)標(biāo)準(zhǔn)庫(kù)和第三方庫(kù)支持較好,但執(zhí)行時(shí)會(huì)額外加載
Blitz或Mono運(yùn)行時(shí)支持(并非完全剝離)。
- 主要應(yīng)用于移動(dòng)端(
使用示例:
- 在
Xamarin.iOS項(xiàng)目中,默認(rèn)Release模式下會(huì)啟用AOT;也可在項(xiàng)目屬性中手動(dòng)開(kāi)啟“Enable LLVM”和“AOT Only”(僅AOT)。 - 在
Unity構(gòu)建iOS時(shí),也會(huì)默認(rèn)將腳本代碼以AOT模式編譯。
優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):符合
iOS平臺(tái)安全/性能需求; - 缺點(diǎn):包體積增大;使用某些需要運(yùn)行時(shí)生成
IL的庫(kù)會(huì)失敗。
AOT 在 .NET 中的演進(jìn)與對(duì)比
| 特性 / 版本 | .NET Core 3.x & .NET 5/6 R2R | .NET 7/8 Native AOT | Mono AOT(Xamarin/Unity) |
|---|---|---|---|
| 最終產(chǎn)物 | 包含 IL + 已編譯部分類(lèi)別的 .dll/.exe | 純本機(jī)可執(zhí)行文件(或較少依賴(lài)文件) | 本機(jī)代碼 + Mono 運(yùn)行時(shí)庫(kù) |
| 運(yùn)行時(shí)依賴(lài) | 依賴(lài) CoreCLR | 零依賴(lài)或極少依賴(lài)(視 SelfContained 而定) | 依賴(lài) Mono 運(yùn)行時(shí) |
| 啟動(dòng)速度 | 明顯優(yōu)于純 JIT,但仍有部分 JIT | 最優(yōu);幾乎無(wú)需運(yùn)行期編譯開(kāi)銷(xiāo) | 較優(yōu)于 JIT,但受限于 Mono 負(fù)載 |
| 支持的應(yīng)用類(lèi)型 | 全部(包括 ASP.NET Core) | 控制臺(tái)、工具類(lèi)應(yīng)用;對(duì) ASP.NET Core 支持有限 | 移動(dòng)端(iOS/Android)、Unity 游戲 |
| 反射 / 動(dòng)態(tài)代碼 | 全量支持;運(yùn)行時(shí)仍可 JIT | 需手動(dòng)指定反射保留;不支持動(dòng)態(tài)生成 IL | 大部分反射可用,但動(dòng)態(tài) IL 支持受限 |
| 發(fā)布包體積 | 較 IL + JIT 稍大 | 可經(jīng)修剪后顯著減??;自行選擇不同運(yùn)行時(shí) | 通常最大,因?yàn)榘?Mono 運(yùn)行時(shí)和 AOT 文件 |
為什么要使用 AOT
加快冷啟動(dòng)
- 對(duì)于啟動(dòng)時(shí)間敏感的應(yīng)用(如命令行工具、微服務(wù)、
Serverless函數(shù)、IoT設(shè)備、移動(dòng)端應(yīng)用等),AOT能顯著減少啟動(dòng)時(shí)等待JIT編譯的開(kāi)銷(xiāo)。
減少運(yùn)行時(shí)依賴(lài)
Native AOT可將運(yùn)行時(shí)(CoreCLR)與垃圾回收等程序集成到一個(gè)可執(zhí)行文件里,實(shí)現(xiàn)“零依賴(lài)”部署。對(duì)于需要極簡(jiǎn)體積或目標(biāo)環(huán)境不允許安裝.NET運(yùn)行時(shí)的場(chǎng)景(比如無(wú)管理員權(quán)限的服務(wù)器、Linux發(fā)行版中未安裝.NET、Docker鏡像瘦身需求),非常有幫助。
提高性能可預(yù)測(cè)性
- 因?yàn)樗写a都在發(fā)布前編譯完成,運(yùn)行時(shí)不會(huì)有突然的
JIT階段,尤其在高并發(fā)場(chǎng)景下,避免了突發(fā)的編譯延遲或CPU峰值。 - 對(duì)于資源受限的環(huán)境(嵌入式、邊緣計(jì)算設(shè)備),可減少
JIT引發(fā)的內(nèi)存和CPU瞬時(shí)占用。
安全性 / 平臺(tái)限制
- 某些平臺(tái)(如
iOS)不允許運(yùn)行時(shí)生成機(jī)器碼(即禁止JIT),必須使用AOT。Mono AOT以及Xamarin都基于此需求在iOS平臺(tái)默認(rèn)強(qiáng)制AOT。
二進(jìn)制可移植性
- 在
Native AOT下,可將生成的可執(zhí)行文件拷貝至目標(biāo)環(huán)境直接運(yùn)行,無(wú)需在目標(biāo)環(huán)境重新編譯,提升交付效率。 AOT 實(shí)現(xiàn)的關(guān)鍵流程
掃描可達(dá)程序集
- 編譯器(
IL to object)需要先掃描所有引用的程序集,收集根節(jié)點(diǎn)(Main方法、動(dòng)態(tài)庫(kù)加載點(diǎn)、反射需求等)。 - 通過(guò)
IL Linker(修剪器)算法,對(duì)樹(shù)狀可達(dá)性做靜態(tài)分析。對(duì)于未標(biāo)記為可達(dá)的代碼進(jìn)行剔除,從而減小體積。
生成本機(jī)代碼
- 將
IL轉(zhuǎn)換為中間的“中間表示”(如RyuJIT IR或LLVM IR),并通過(guò)平臺(tái)本地編譯器(例如MSVC、LLVM)優(yōu)化后生成對(duì)應(yīng)體系結(jié)構(gòu)的機(jī)器碼。 - 同時(shí)將
GC、類(lèi)型元數(shù)據(jù)、異常處理表等運(yùn)行時(shí)信息打包到可執(zhí)行文件中。
處理反射 / 動(dòng)態(tài)需求
- 因?yàn)榫幾g時(shí)無(wú)法窺見(jiàn)運(yùn)行時(shí)可能使用的反射類(lèi)型,需要開(kāi)發(fā)者通過(guò)屬性或
XML(.rd.xml)聲明“需要保留”的類(lèi)型/程序集/成員,否則編譯器會(huì)在做“修剪”時(shí)誤刪這些代碼。
.NET 7+ 用 DynamicDependencyAttribute、PreserveDependency 等方式告知編譯器保留反射訪問(wèn)所需類(lèi)型。
生產(chǎn)最終可執(zhí)行文件
- 靜態(tài)地將機(jī)器碼、元數(shù)據(jù)、運(yùn)行時(shí)庫(kù)等整合成一個(gè)單一文件,或者如
Linux下分為可執(zhí)行 + 一些.so文件。 - 通過(guò)
dotnet publish -c Release -r <RID> /p:PublishAot=true完成。
如何在項(xiàng)目中啟用 AOT
ReadyToRun(R2R)示例
在 .csproj 中添加屬性:
<PropertyGroup> <!-- 啟用 ReadyToRun 預(yù)編譯 --> <PublishReadyToRun>true</PublishReadyToRun> <!-- 使用 CrossGen2 進(jìn)行預(yù)編譯(建議在 .NET 6+ 使用) --> <PublishReadyToRunUseCrossgen2>true</PublishReadyToRunUseCrossgen2> <!-- 可選:指定是否在發(fā)布時(shí)生成非托管符號(hào)文件 --> <PublishReadyToRunEmitSymbols>true</PublishReadyToRunEmitSymbols> <!-- 指定具體目標(biāo)平臺(tái) --> <RuntimeIdentifier>win-x64</RuntimeIdentifier> </PropertyGroup>
然后執(zhí)行:
dotnet publish -c Release -r win-x64 --self-contained false
Native AOT 示例
在 .csproj 中添加:
<PropertyGroup> <!-- 開(kāi)啟 Native AOT 發(fā)布 --> <PublishAot>true</PublishAot> <!-- 開(kāi)啟修剪,減小體積 --> <PublishTrimmed>true</PublishTrimmed> <!-- 例如發(fā)布為控制臺(tái)應(yīng)用,可選改為 false 如果涉及 WinForms/WPF --> <SelfContained>true</SelfContained> <!-- 目標(biāo)運(yùn)行時(shí)標(biāo)識(shí)符 --> <RuntimeIdentifier>win-x64</RuntimeIdentifier> <!-- 可選:發(fā)布時(shí)同時(shí)生成調(diào)試符號(hào) --> <DebugType>embedded</DebugType> <!-- 如需使用單文件發(fā)布 --> <PublishSingleFile>true</PublishSingleFile> <!-- 可選:剔除 Diagnostics 診斷支持以進(jìn)一步減小體積 --> <InvariantGlobalization>true</InvariantGlobalization> </PropertyGroup>
然后運(yùn)行:
dotnet publish -c Release
注意:
- 如果項(xiàng)目中使用到了反射(例如
Activator.CreateInstance、Type.GetType、JsonSerializer等),編譯時(shí)需要添加對(duì)應(yīng)的保留配置,否則會(huì)出現(xiàn)無(wú)法找到類(lèi)型或成員的運(yùn)行時(shí)異常。 - 對(duì)于依賴(lài)第三方
NuGet包且該包使用了動(dòng)態(tài)特性,也需要檢查是否AOT兼容;部分庫(kù)需要手動(dòng)編寫(xiě)TrimmerRootAssembly或自定義rd.xml。
AOT 的優(yōu)缺點(diǎn)及適用場(chǎng)景
優(yōu)點(diǎn)
極快啟動(dòng):
- 省去運(yùn)行時(shí)
JIT階段,首屏響應(yīng)或啟動(dòng)速度幾乎與本機(jī)程序無(wú)差異。
部署簡(jiǎn)單:
Native AOT模式下可執(zhí)行文件自包含所有依賴(lài),無(wú)需目標(biāo)機(jī)器預(yù)先安裝.NET運(yùn)行時(shí)。
更小內(nèi)存占用峰值:
- 通過(guò)修剪技術(shù)剔除未使用的代碼和依賴(lài),運(yùn)行時(shí)加載更輕量。
可預(yù)測(cè)走向發(fā)布:
- 編譯時(shí)已做全部?jī)?yōu)化與檢查,減少運(yùn)行期“編譯出錯(cuò)”或動(dòng)態(tài)缺失依賴(lài)的問(wèn)題。
符合某些平臺(tái)限制:
- 比如在
iOS平臺(tái)禁止JIT,通過(guò)Mono AOT或Xamarin iOS編譯可滿(mǎn)足Apple審核要求。
缺點(diǎn)
體積膨脹 vs 兼容性:
ReadyToRun 相對(duì)于純 IL 包增量并不大,但 Native AOT 若不精心修剪,體積可能與完整運(yùn)行時(shí)相當(dāng);
某些第三方庫(kù)對(duì) AOT 支持不好,可能需要額外適配。
有限的運(yùn)行時(shí)代碼生成:
無(wú)法做 Reflection.Emit、動(dòng)態(tài)生成表達(dá)式樹(shù)等,需要在編譯期預(yù)先聲明;
運(yùn)行時(shí)使用 System.Text.Json、Newtonsoft.Json 等反射型序列化/反序列化時(shí),需手動(dòng)配置 JsonSerializerContext 或顯式注冊(cè)要序列化的類(lèi)型。
調(diào)試和診斷不便:
Native AOT 下 StackTrace 可能缺少符號(hào)映射;
如出現(xiàn) CPU 性能或內(nèi)存泄漏問(wèn)題,無(wú)法借助 JIT 時(shí)代的動(dòng)調(diào)。
不適合大型動(dòng)態(tài)場(chǎng)景:
- 如果項(xiàng)目本身依賴(lài)插件熱加載、腳本引擎、或者大量運(yùn)行時(shí)元編程,就不適合
Native AOT。
典型適用場(chǎng)景
命令行工具(CLI)
- 比如一些
Git擴(kuò)展工具、DevOps腳本工具、跨平臺(tái)部署時(shí),Native AOT可做到零依賴(lài)、秒級(jí)啟動(dòng)。
微服務(wù)/Serverless 函數(shù)
- 在容器或云函數(shù)中,冷啟動(dòng)時(shí)間至關(guān)重要;使用
AOT或R2R可降低冷啟動(dòng)延遲。
桌面/移動(dòng)端輕量應(yīng)用
- 某些場(chǎng)景下需要小體積、無(wú)運(yùn)行時(shí)依賴(lài),或目標(biāo)平臺(tái)禁止
JIT,可考慮AOT。
IoT/嵌入式設(shè)備
- 在資源受限的硬件上,減少運(yùn)行時(shí)占用,提升響應(yīng)速度。 創(chuàng)建一個(gè) Native AOT 控制臺(tái)應(yīng)用
創(chuàng)建項(xiàng)目
dotnet new console -n AotDemo cd AotDemo
修改項(xiàng)目文件 AotDemo.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<!-- 開(kāi)啟 Native AOT -->
<PublishAot>true</PublishAot>
<!-- 發(fā)布時(shí)進(jìn)行修剪 -->
<PublishTrimmed>true</PublishTrimmed>
<!-- 將所有依賴(lài)打包到單個(gè)可執(zhí)行文件里 -->
<PublishSingleFile>true</PublishSingleFile>
<!-- 自包含部署(包含運(yùn)行時(shí)) -->
<SelfContained>true</SelfContained>
<!-- 運(yùn)行時(shí)標(biāo)識(shí)符,根據(jù)目標(biāo)平臺(tái)調(diào)整 -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<!-- 便于調(diào)試,可以嵌入 PDB 符號(hào) -->
<DebugType>embedded</DebugType>
</PropertyGroup>
</Project>編寫(xiě)簡(jiǎn)單代碼 Program.cs
using System;
using System.Reflection;
namespace AotDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello Native AOT World!");
// 示例:試圖使用反射讀取自身類(lèi)型
var type = typeof(Program);
Console.WriteLine($"當(dāng)前類(lèi)型:{type.FullName}");
}
}
}編譯并發(fā)布
在項(xiàng)目根目錄執(zhí)行:
dotnet publish -c Release
發(fā)布完成后,打開(kāi) bin\Release\net7.0\win-x64\publish\,可以看到 AotDemo.exe 或 AotDemo
運(yùn)行與驗(yàn)證
直接雙擊或命令行執(zhí)行 AotDemo.exe 或 ./AotDemo,輸出:
Hello Native AOT World!
當(dāng)前類(lèi)型:AotDemo.Program
分析產(chǎn)物體積
- 若沒(méi)有配置修剪(
<PublishTrimmed>true</PublishTrimmed>),可執(zhí)行文件體積約在 30–50MB 左右。 - 啟用修剪后,一般可以減小到 10–20MB(視代碼復(fù)雜度及所引用包而定)。
如果使用了反射
- 在
Native AOT中直接調(diào)用typeof(Program)讀取自身類(lèi)型是可以的,因?yàn)榫幾g器會(huì)保留Program類(lèi); - 如果反射調(diào)用一個(gè)僅在運(yùn)行期才可確定的類(lèi)型(例如字符串拼接得到的類(lèi)型名稱(chēng)),編譯時(shí)無(wú)法知道,就需要在項(xiàng)目里添加
rd.xml或使用DynamicDependency屬性顯式聲明保留該類(lèi)型。
診斷和調(diào)試
- 對(duì)于
Native AOT,調(diào)試體驗(yàn)不如JIT平滑,特別是斷點(diǎn)、StackTrace、Debug.Assert之類(lèi)需要符號(hào)的場(chǎng)景。 - 建議:僅在開(kāi)發(fā)階段默認(rèn)關(guān)閉
AOT,保留JIT類(lèi)庫(kù)的常規(guī)調(diào)試;發(fā)布前再切換至AOT。
rd.xml 配置文件
作用
在 .NET Native AOT、ReadyToRun + 修剪(ILLinker)等場(chǎng)景中,編譯器或 IL 鏈接器會(huì)在發(fā)布階段對(duì)中間語(yǔ)言(IL)進(jìn)行分析與“修剪”(Trim),剔除未被靜態(tài)調(diào)用或引用的類(lèi)型、成員與元數(shù)據(jù),以減小輸出體積、提升啟動(dòng)性能。然而,反射(Reflection)是一種運(yùn)行時(shí)特性:代碼中的某些類(lèi)型、方法、屬性等只有在運(yùn)行階段通過(guò)反射動(dòng)態(tài)訪問(wèn),編譯時(shí)并不可見(jiàn)。若不做額外保留,ILLinker 在靜態(tài)分析時(shí)會(huì)誤判這些成員為“不可達(dá)”,進(jìn)而被剔除,導(dǎo)致在運(yùn)行時(shí)使用諸如 Activator.CreateInstance、Type.GetType、序列化/反序列化、ORM 映射等場(chǎng)景拋出 “找不到類(lèi)型/成員” 的異常。
.rd.xml 文件是 .NET Native 與 ILLink 場(chǎng)景下的“保留指令”描述文件(runtime directives XML)。通過(guò)在其中顯式聲明“要保留的程序集/類(lèi)型/成員/屬性”,可以避免它們?cè)诎l(fā)布構(gòu)建時(shí)被錯(cuò)誤剔除,從而保證反射相關(guān)邏輯在運(yùn)行時(shí)正常工作。
基本結(jié)構(gòu)
一個(gè)典型的 .rd.xml(Runtime Directives XML)文件的根節(jié)點(diǎn)為 <Directives>,其下通常有一個(gè) <Application> 或 <Library> 節(jié)點(diǎn),后者根據(jù)項(xiàng)目類(lèi)型(控制臺(tái)應(yīng)用、類(lèi)庫(kù)等)有所不同。
<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.com/netcore/2013/01/metadata">
<!--
Application: 用于標(biāo)記應(yīng)用程序可達(dá)的根節(jié)點(diǎn);
Library: 用于類(lèi)庫(kù)時(shí)的配置(一般也可放在 Application 節(jié)點(diǎn)中)。
-->
<Application>
<!-- 在這里聲明要保留的程序集、類(lèi)型、成員等 -->
</Application>
</Directives>其中常用的子節(jié)點(diǎn)包括:
<Assembly Name="..." [Dynamic="..."] [Serialize="..."]>:標(biāo)記要保留的程序集,以及對(duì)該程序集下類(lèi)型的保留策略。<Type Name="..." [Dynamic="..."] [Serialize="..."]>:在某個(gè)<Assembly>內(nèi)部,用于配置要保留的類(lèi)型(類(lèi)、接口、結(jié)構(gòu)體、枚舉等)。常見(jiàn)的屬性:Dynamic="Required All":保留該類(lèi)型的所有成員(字段、屬性、方法、事件等)以滿(mǎn)足動(dòng)態(tài)訪問(wèn)。Serialize="Required Public":僅保留該類(lèi)型公共可序列化成員,供 JSON/XML 序列化時(shí)使用。
<Method Name="..."、<Field Name="..."、<Property Name="..."等子節(jié)點(diǎn):進(jìn)一步精細(xì)到單個(gè)成員級(jí)別的保留配置。
常用配置項(xiàng)說(shuō)明
在 <Assembly> 和 <Type> 節(jié)點(diǎn)上,常見(jiàn)的屬性及含義如下:
- Dynamic=“Required All” / “Required Public” / “Required PublicAndCritical” 等
Required All:保留該節(jié)點(diǎn)下所有成員(字段、屬性、方法、事件等,無(wú)論是否為public或private),用于某些場(chǎng)景需要完全動(dòng)態(tài)訪問(wèn)。Required Public:僅保留公共(public)成員。Required PublicAndCritical:保留公共成員以及具有安全Critical特性。
- Serialize=“Required Public” / “Required All”
- 主要用于
JSON/XML序列化場(chǎng)景:保留類(lèi)型的公有字段與屬性,以便在運(yùn)行時(shí)的序列化/反序列化機(jī)制(如System.Text.Json或XmlSerializer)能夠正常工作。 - 與
Dynamic同時(shí)存在時(shí),序列化場(chǎng)景保留的成員更加精準(zhǔn)(避免把每個(gè)私有成員都打包進(jìn)二進(jìn)制)。
- 主要用于
- Collections
- 如果類(lèi)型中有對(duì)泛型集合(
List<T>)的反射訪問(wèn)(例如Activator.CreateInstance(typeof(List<>).MakeGenericType(...))),需要通過(guò)GenericInstantiation子節(jié)點(diǎn)顯式聲明泛型實(shí)例化需求。
- 如果類(lèi)型中有對(duì)泛型集合(
- Version / Culture / PublicKeyToken
<Assembly Name="Name" Version="1.0.0.0" Culture="neutral" PublicKeyToken="abcdef1234567890">- 當(dāng)引用了特定強(qiáng)命名程序集中類(lèi)型,需要寫(xiě)明版本號(hào)與公鑰令牌,才能準(zhǔn)確匹配。若不寫(xiě)
Version/Culture/PublicKeyToken,ILLink會(huì)嘗試做寬松匹配(僅匹配Name)。
.rd.xml 示例演示
保留整個(gè)程序集(全部類(lèi)型和成員)
假設(shè)項(xiàng)目中有一個(gè) MyApp.Core.dll,并且在運(yùn)行時(shí)會(huì)通過(guò)反射訪問(wèn)其中的所有類(lèi)型(如插件、動(dòng)態(tài)加載等)。若要保留 MyApp.Core 程序集中所有內(nèi)容,可以這樣寫(xiě):
<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.com/netcore/2013/01/metadata">
<Application>
<!-- 忽略版本號(hào)、Culture、PublicKeyToken,以寬松方式匹配 MyApp.Core -->
<Assembly Name="MyApp.Core" Dynamic="Required All" Serialize="Required All" />
</Application>
</Directives>保留特定類(lèi)型(及其成員)
若只希望針對(duì)某個(gè)類(lèi)型(如 MyApp.Core.Services.PluginLoader)進(jìn)行反射保留:
<Assembly Name="MyApp.Core">
<Type Name="MyApp.Core.Models.Entity`1" Dynamic="Required All">
<!-- 指定泛型實(shí)例化需求 -->
<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>若該類(lèi)型是泛型類(lèi)型,例如 MyApp.Core.Models.Entity<T>,且會(huì)在運(yùn)行時(shí)實(shí)例化某個(gè)具體泛型(如 Entity<Customer>)進(jìn)行反射構(gòu)造,需要這樣指定:
<Assembly Name="MyApp.Core">
<Type Name="MyApp.Core.Models.Entity`1" Dynamic="Required All">
<!-- 指定泛型實(shí)例化需求 -->
<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>- 其中反引號(hào)后 1 表示一參泛型;
<GenericInstantiation>內(nèi)指定了要實(shí)例化的具體泛型參數(shù)類(lèi)型,必須給出該類(lèi)型的程序集、版本、Culture、PublicKeyToken 等完整信息;否則無(wú)法被正確保留。
保留 JSON(或其他序列化)所需成員
在使用 System.Text.Json 的源生成(source generator)方式時(shí),可以通過(guò) [JsonSerializable] 等特性生成上下文,通常無(wú)需 .rd.xml。但如果使用反射式序列化(如 Newtonsoft.Json 的默認(rèn)行為),則需要保留類(lèi)型的公共 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 序列化能訪問(wèn)到 Id、Name、SecretCode,可以這樣配置:
<Directives xmlns="http://schemas.microsoft.com/netcore/2013/01/metadata">
<Application>
<Assembly Name="MyApp.Core">
<!--
保留 Customer 類(lèi)型的公有屬性和字段
Serialize="Required Public" 表示僅保留 public 字段/屬性
-->
<Type Name="MyApp.Core.Models.Customer" Dynamic="Required Public" Serialize="Required Public" />
</Assembly>
</Application>
</Directives>這里 Dynamic="Required Public" 也會(huì)保留公有方法,若無(wú)需公有方法也可只用 Serialize="Required Public"。如果只想保留序列化場(chǎng)景的 public 屬性,把 Dynamic 去掉或設(shè)為更窄范圍也可行。
保留特定成員(Method / Field / Property)
有時(shí)只需保留某個(gè)類(lèi)型下的某個(gè)方法或字段,而不是整類(lèi)型
<Directives xmlns="http://schemas.microsoft.com/netcore/2013/01/metadata">
<Application>
<Assembly Name="MyApp.Core">
<Type Name="MyApp.Core.Utils.Helper">
<!-- 僅保留名為 DoWork 的公有實(shí)例方法 -->
<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>節(jié)點(diǎn)均需要寫(xiě)上 Name;Dynamic值可針對(duì)該節(jié)點(diǎn)保留范圍進(jìn)行調(diào)整。
在項(xiàng)目中集成 .rd.xml
將 .rd.xml 文件放到項(xiàng)目根目錄并在 .csproj 中進(jìn)行聲明,確保編譯時(shí)能被識(shí)別并生效。
在項(xiàng)目根目錄(與 .csproj 同級(jí))放置文件,命名為 rd.xml,在 .csproj 中引用 .rd.xml
<PropertyGroup> <!-- 啟用 Native AOT 發(fā)布 --> <PublishAot>true</PublishAot> <!-- 啟用修剪 --> <PublishTrimmed>true</PublishTrimmed> <!-- 標(biāo)記要使用 rd.xml 作為保留指令 --> <TrimmerDefaultAction>link</TrimmerDefaultAction> <TrimmerRootDescriptorFiles>rd.xml</TrimmerRootDescriptorFiles> <!-- 其他 AOT 相關(guān)配置 --> <RuntimeIdentifier>win-x64</RuntimeIdentifier> <PublishSingleFile>true</PublishSingleFile> <SelfContained>true</SelfContained> </PropertyGroup>
TrimmerDefaultAction:link表示默認(rèn)修剪所有未被標(biāo)記為保留的代碼;copy表示復(fù)制所有引用的程序集但不修剪(相當(dāng)于R2R模式);
TrimmerRootDescriptorFiles:指定一個(gè)或多個(gè).rd.xml文件路徑,多個(gè)文件以分號(hào)分隔。這里使用相對(duì)路徑rd.xml。
編譯與發(fā)布命令
dotnet publish -c Release
編譯器會(huì)自動(dòng)讀取 rd.xml,根據(jù)其中配置的保留指令對(duì)代碼進(jìn)行修剪,確保運(yùn)行時(shí)反射需求的類(lèi)型與成員被正確保留。
調(diào)試與驗(yàn)證
- 啟用鏈接器診斷日志
在 .csproj 中添加或在命令行指定:
<PropertyGroup> <!-- 打印鏈接器日志到指定文件 --> <TrimmerLogFile>trim-log.xml</TrimmerLogFile> <!-- 顯示鏈接器分析的詳細(xì)級(jí)別(可選:Skip、Silent、Verbose、diagnostic) --> <TrimmerLogLevel>diagnostic</TrimmerLogLevel> </PropertyGroup>
發(fā)布后會(huì)在輸出目錄生成 trim-log.xml,打開(kāi)后查找是否有某個(gè)類(lèi)型/成員被修剪或被保留的記錄。診斷級(jí)別輸出會(huì)非常詳細(xì),便于查找“保留”是否生效,或哪些類(lèi)型因遺漏而被剔除。
- 運(yùn)行時(shí)測(cè)試反射調(diào)用
- 在發(fā)布后拷貝到干凈環(huán)境,手動(dòng)調(diào)用關(guān)鍵反射邏輯,驗(yàn)證是否拋出
TypeLoadException、MissingMethodException等。 - 如果依舊報(bào)錯(cuò),查看
trim-log.xml中對(duì)應(yīng)類(lèi)型/成員是否被剔除,若剔除則需要在rd.xml做進(jìn)一步保留。
- 在發(fā)布后拷貝到干凈環(huán)境,手動(dòng)調(diào)用關(guān)鍵反射邏輯,驗(yàn)證是否拋出
- 使用
ILSpy / dotnet-ildasm工具檢查輸出- 可對(duì)生成后的可執(zhí)行文件或
.dll在反編譯工具(ILSpy、dnSpy)中查看某些類(lèi)型是否還存在。 - 或者用
dotnet-ildasm導(dǎo)出元數(shù)據(jù),再搜索對(duì)應(yīng)類(lèi)型/成員名。
- 可對(duì)生成后的可執(zhí)行文件或
到此這篇關(guān)于.NET AOT 詳解的文章就介紹到這了,更多相關(guān).NET AOT內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Asp.net下實(shí)現(xiàn)變長(zhǎng)連接的web即時(shí)應(yīng)用的實(shí)現(xiàn)范例及ReverseAjax的演示介紹
根據(jù)公司近期的一個(gè)培訓(xùn)整理的資料,附件包括一個(gè)完整的使用變長(zhǎng)連接的web即時(shí)聊天室的范例和針對(duì)ReverseAjax的ppt培訓(xùn)文稿,其中ppt中包含了對(duì)范例程序的完整講解2011-12-12
.NET使用System.Timers.Timer類(lèi)實(shí)現(xiàn)程序定時(shí)執(zhí)行
這篇文章介紹了.NET使用System.Timers.Timer類(lèi)實(shí)現(xiàn)程序定時(shí)執(zhí)行的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
subsonic3.0插件更新字符串過(guò)長(zhǎng)引發(fā)的異常修復(fù)方法
這篇文章主要介紹了subsonic3.0插件更新字符串過(guò)長(zhǎng)引發(fā)的異常修復(fù)方法,需要的朋友可以參考下2014-04-04
ASP.NET MVC 中實(shí)現(xiàn)基于角色的權(quán)限控制的處理方法
在ASP.NET MVC中,通過(guò)使用其所提供的內(nèi)置2013-03-03
ASP.NET Core2讀寫(xiě)InfluxDB時(shí)序數(shù)據(jù)庫(kù)的方法教程
Influxdb是一個(gè)開(kāi)源的分布式時(shí)序、時(shí)間和指標(biāo)數(shù)據(jù)庫(kù),使用go語(yǔ)言編寫(xiě),無(wú)需外部依賴(lài),下面這篇文章主要給大家介紹了關(guān)于ASP.NET Core2讀寫(xiě)InfluxDB時(shí)序數(shù)據(jù)庫(kù)的相關(guān)資料,需要的朋友可以參考下2018-11-11
Razor TagHelper實(shí)現(xiàn)Markdown轉(zhuǎn)HTML的方法
下面小編就為大家分享一篇Razor TagHelper實(shí)現(xiàn)Markdown轉(zhuǎn)HTML的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
IP地址與整數(shù)之間的轉(zhuǎn)換實(shí)現(xiàn)代碼(asp.net)
把這個(gè)整數(shù)轉(zhuǎn)換成一個(gè)32位二進(jìn)制數(shù)。從左到右,每8位進(jìn)行一下分割,得到4段8位的二進(jìn)制數(shù),把這些二進(jìn)制數(shù)轉(zhuǎn)換成整數(shù)然后加上”?!本褪沁@個(gè)ip地址了2012-09-09

