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

