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

.NET?AOT?詳解

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

簡介

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

使用示例

<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.iOSUnity iOS 構(gòu)建等);
    • 編譯過程會在打包時將 IL 轉(zhuǎn)為目標(biāo)架構(gòu)的本機(jī)代碼;
    • 對大多數(shù)標(biāo)準(zhǔn)庫和第三方庫支持較好,但執(zhí)行時會額外加載 BlitzMono 運(yùn)行時支持(并非完全剝離)。

使用示例:

  • 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 AOTMono 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 IRLLVM 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 AOTXamarin 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 AOTStackTrace 可能缺少符號映射;

如出現(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)重要;使用 AOTR2R 可降低冷啟動延遲。

桌面/移動端輕量應(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.exeAotDemo

運(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 NativeILLink 場景下的“保留指令”描述文件(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)下所有成員(字段、屬性、方法、事件等,無論是否為 publicprivate),用于某些場景需要完全動態(tài)訪問。
    • Required Public:僅保留公共(public)成員。
    • Required PublicAndCritical:保留公共成員以及具有安全 Critical 特性。
  • Serialize=“Required Public” / “Required All”
    • 主要用于 JSON/XML 序列化場景:保留類型的公有字段與屬性,以便在運(yùn)行時的序列化/反序列化機(jī)制(如 System.Text.JsonXmlSerializer)能夠正常工作。
    • 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)一步保留。
  • 使用 ILSpy / dotnet-ildasm 工具檢查輸出
    • 可對生成后的可執(zhí)行文件或 .dll 在反編譯工具(ILSpy、dnSpy)中查看某些類型是否還存在。
    • 或者用 dotnet-ildasm 導(dǎo)出元數(shù)據(jù),再搜索對應(yīng)類型/成員名。

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

相關(guān)文章

最新評論