欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

.NET?AOT?詳解

 更新時(shí)間:2025年06月12日 10:02:28   作者:我是唐青楓  
AOT是一種將代碼直接編譯為機(jī)器碼的技術(shù),與傳統(tǒng)的?JIT(Just-In-Time?Compilation)編譯方式形成對(duì)比,本文給大家介紹.NET?AOT的相關(guān)知識(shí),感興趣的朋友一起看看吧

簡(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ò) crossgencrossgen2 工具,將 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)于 JITR2R;
    • 可實(shí)現(xiàn)瘦二進(jìn)制(使用“修剪”(trimming)技術(shù)去除未使用的程序集和類(lèi)型);
    • 對(duì)反射、動(dòng)態(tài)代碼支持有限,需要手動(dòng)配置 rd.xmlTrimmerRootAssembly、DynamicDependency 等顯式保留信息;
    • 目前僅支持 控制臺(tái)應(yīng)用/單一可執(zhí)行體,不支持 ASP.NET Core 完整框架(僅支持最小化API)。

使用示例

<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.iOSUnity 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ì)額外加載 BlitzMono 運(yùn)行時(shí)支持(并非完全剝離)。

使用示例:

  • 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 AOTMono 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 IRLLVM 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.CreateInstanceType.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 AOTXamarin 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 AOTStackTrace 可能缺少符號(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)重要;使用 AOTR2R 可降低冷啟動(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.exeAotDemo

運(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 NativeILLink 場(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ú)論是否為 publicprivate),用于某些場(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.JsonXmlSerializer)能夠正常工作。
    • 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í)例化需求。
  • 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)一步保留。
  • 使用 ILSpy / dotnet-ildasm 工具檢查輸出
    • 可對(duì)生成后的可執(zhí)行文件或 .dll 在反編譯工具(ILSpy、dnSpy)中查看某些類(lèi)型是否還存在。
    • 或者用 dotnet-ildasm 導(dǎo)出元數(shù)據(jù),再搜索對(duì)應(yīng)類(lèi)型/成員名。

到此這篇關(guān)于.NET AOT 詳解的文章就介紹到這了,更多相關(guān).NET AOT內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論