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

.NET NativeAOT 用法指南

 更新時(shí)間:2025年01月23日 09:26:54   作者:dotNET跨平臺(tái)  
NativeAOT是.NET 8中引入的一種編譯方式,它將代碼編譯成原生代碼,提高性能并節(jié)省資源,然而,NativeAOT也帶來(lái)了一些問(wèn)題,如反射依賴、泛型實(shí)例化等,本文詳細(xì)介紹了如何使用NativeAOT以及如何解決這些常見(jiàn)問(wèn)題,感興趣的朋友跟隨小編一起看看吧

隨著 .NET 8 的發(fā)布,一種新的“時(shí)尚”應(yīng)用模型 NativeAOT 開(kāi)始在各種真實(shí)世界的應(yīng)用中廣泛使用。

除了對(duì) NativeAOT 工具鏈的基本使用外,“NativeAOT”一詞還帶有原生世界的所有限制,因此您必須知道如何處理這些問(wèn)題才能正確使用它。

在這篇博客中,我將討論它們。

基本用法

使用 NativeAOT 非常簡(jiǎn)單,只需要在發(fā)布應(yīng)用時(shí)使用 MSBuild 傳遞一個(gè)屬性 PublishAot=true 即可。

通常,它可以是:

dotnet publish -c Release -r win-x64 /p:PublishAot=true

其中 win-x64 是運(yùn)行時(shí)標(biāo)識(shí)符,可以替換為 linux-x64,osx-arm64 或其他平臺(tái)。您必須指定它,因?yàn)?NativeAOT 需要為您指定的運(yùn)行時(shí)標(biāo)識(shí)符生成原生代碼。

然后發(fā)布的應(yīng)用可以在 bin/Release/<target framework>/<runtime identifier>/publish 中找到

關(guān)于編譯

在討論使用 NativeAOT 時(shí)可能遇到的各種問(wèn)題的解決方案之前,我們需要稍微深入一點(diǎn),看看 NativeAOT 是如何編譯代碼的。

我們經(jīng)常聽(tīng)說(shuō) NativeAOT 會(huì)剪裁掉沒(méi)有被使用的代碼。而實(shí)際上,它并不像 IL 剪裁那樣從程序集中剪裁掉不必要的代碼,而是只編譯代碼中引用的東西。

NativeAOT 編譯包括兩個(gè)階段:

  • 掃描 IL 代碼,構(gòu)建整個(gè)程序視圖(一個(gè)依賴圖),其中包含所有需要編譯的必要依賴節(jié)點(diǎn)。
  • 對(duì)依賴圖中的每個(gè)方法進(jìn)行實(shí)際的編譯,生成代碼。

請(qǐng)注意,在編譯過(guò)程中可能會(huì)出現(xiàn)一些“延遲”的依賴,因此上述兩個(gè)階段可能會(huì)交錯(cuò)出現(xiàn)。

這意味著,在分析過(guò)程中沒(méi)有被計(jì)算為依賴的任何東西最終都不會(huì)被編譯。

反射

依賴圖是在編譯期間靜態(tài)構(gòu)建的,這也意味著任何無(wú)法靜態(tài)分析的東西都不會(huì)被編譯。不幸的是,反射,即在不事先告訴編譯器的情況下在運(yùn)行時(shí)獲取東西,正是編譯器無(wú)法弄清楚的一件事。

NativeAOT 編譯器有一些能力可以根據(jù)編譯時(shí)的字面量來(lái)推斷出反射調(diào)用需要什么東西。

例如:

var type = Type.GetType("Foo");
Activator.CreateInstance(type);
class Foo
{
    public Foo() => Console.WriteLine("Foo instantiated");
}

上面的反射目標(biāo)(即 Foo)可以被編譯器弄清楚,因?yàn)榫幾g器可以看到你試圖獲取類型 Foo,所以類型 Foo 會(huì)被標(biāo)記為一個(gè)依賴,這導(dǎo)致 Foo 被編譯到最終的產(chǎn)物中。

如果你運(yùn)行這個(gè)程序,它會(huì)如預(yù)期地打印 Foo instantiated。

但是如果我們將代碼改為如下:

var type = Type.GetType(Console.ReadLine());
Activator.CreateInstance(type);
class Foo
{
    public Foo() => Console.WriteLine("Foo instantiated");
}

現(xiàn)在讓我們用 NativeAOT 構(gòu)建并運(yùn)行這個(gè)程序,然后輸入 Foo 來(lái)創(chuàng)建一個(gè) Foo 的實(shí)例。你會(huì)立刻得到一個(gè)異常:

Unhandled Exception: System.ArgumentNullException: Value cannot be null. (Parameter 'type')
   at System.ArgumentNullException.Throw(String) + 0x2b
   at System.ActivatorImplementation.CreateInstance(Type, Boolean) + 0xe7
   ...

這是因?yàn)榫幾g器無(wú)法看到你在哪里使用了 Foo,所以它根本不會(huì)為 Foo 生成任何代碼,導(dǎo)致這里的 type 為 null。

此外,依賴分析是精確到單個(gè)方法的,這意味著即使一個(gè)類型被認(rèn)為是一個(gè)依賴,如果該類型中的任何方法沒(méi)有被使用,該方法也不會(huì)被包含在代碼生成中。

雖然這可以通過(guò)將所有類型和方法添加到依賴圖中來(lái)解決,這樣編譯器就會(huì)為它們生成代碼。這就是 TrimmerRootAssembly 的作用:通過(guò)提供 TrimmerRootAssembly,NativeAOT 編譯器會(huì)將你指定的程序集中的所有東西都作為根。

但是涉及泛型的情況就不是這樣了。

動(dòng)態(tài)泛型實(shí)例化

在 .NET 中,我們有泛型,編譯器會(huì)為每個(gè)非共享的泛型類型和方法生成不同的代碼。

假設(shè)我們有一個(gè)類型 Point<T>

struct Point<T>
{
    public T X, Y;
}

如果我們有一段代碼試圖使用 Point<int>,編譯器會(huì)為 Point<int> 生成專門(mén)的代碼,使得 Point.X 和 Point.Y 都是 int。如果我們有一個(gè) Point<float>,編譯器會(huì)生成另一個(gè)專門(mén)的代碼,使得 Point.X 和 Point.Y 都是 float

通常情況下,這不會(huì)導(dǎo)致任何問(wèn)題,因?yàn)榫幾g器可以靜態(tài)地找出你在代碼中使用的所有實(shí)例化,直到你試圖使用反射來(lái)構(gòu)造一個(gè)泛型類型或一個(gè)泛型方法:

var type = Type.GetType(Console.ReadLine());
var pointType = typeof(Point<>).MakeGenericType(type);

上面的代碼在 NativeAOT 下不會(huì)工作,因?yàn)榫幾g器無(wú)法推斷出 Point<T> 的實(shí)例化,所以編譯器既不會(huì)生成 Point<int> 的代碼,也不會(huì)生成 Point<float> 的代碼。

盡管編譯器可以為 intfloat,甚至泛型類型定義 Point<> 生成代碼,但是如果編譯器沒(méi)有生成 Point<int> 的實(shí)例化代碼,你就無(wú)法使用 Point<int>。

即使你使用 TrimmerRootAssembly 來(lái)告訴編譯器將你的程序集中的所有東西都作為根,也仍然不會(huì)為像 Point<int> 或 Point<float> 這樣的實(shí)例化生成代碼,因?yàn)樗鼈冃枰鶕?jù)類型參數(shù)來(lái)單獨(dú)構(gòu)造。

解決方案

既然我們已經(jīng)找出了在 NativeAOT 下可能發(fā)生的潛在問(wèn)題,讓我們來(lái)談?wù)劷鉀Q方案。

在其他地方使用它

最簡(jiǎn)單的想法是,我們可以通過(guò)在代碼中使用它來(lái)讓編譯器知道我們需要什么。

例如,對(duì)于代碼

var type = Type.GetType(Console.ReadLine());
var pointType = typeof(Point<>).MakeGenericType(type);

只要我們知道我們要使用 Point<int> 和 Point<float>,我們可以在其他地方使用它一次,然后編譯器就會(huì)為它們生成代碼:

// 我們使用一個(gè)永遠(yuǎn)為假的條件來(lái)確保代碼不會(huì)被執(zhí)行
// 因?yàn)槲覀冎幌胱尵幾g器知道依賴關(guān)系
// 注意,如果我們?cè)谶@里簡(jiǎn)單地使用一個(gè) `if (false)`
// 這個(gè)分支會(huì)被編譯器完全移除,因?yàn)樗嵌嘤嗟?
// 所以,讓我們?cè)谶@里使用一個(gè)不平凡但不可能的條件
if (DateTime.Now.Year < 0)
{
    var list = new List<Type>();
    list.Add(typeof(Point<int>));
    list.Add(typeof(Point<float>));
}

DynamicDependency

我們有一個(gè)屬性 DynamicDependencyAttribute 來(lái)告訴編譯器一個(gè)方法依賴于另一個(gè)類型或方法。

所以我們可以利用它來(lái)告訴編譯器:“如果 A 被包含在依賴圖中,那么也添加 B”。

下面是一個(gè)例子:

class Foo
{
    readonly Type t = typeof(Bar);
    [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(Bar))]
    public void A()
    {
        foreach (var prop in t.GetProperties())
        {
            Console.WriteLine(prop);
        }
    }
}
class Bar
{
    public int X { get; set; }
    public int Y { get; set; }
}

現(xiàn)在只要編譯器發(fā)現(xiàn)有任何代碼路徑調(diào)用了 Foo.A,Bar 中的所有公共屬性都會(huì)被添加到依賴圖中,這樣我們就能夠?qū)?nbsp;Bar 的每個(gè)公共屬性進(jìn)行動(dòng)態(tài)反射調(diào)用。

這個(gè)屬性還有許多重載,可以接受不同的參數(shù)來(lái)適應(yīng)不同的用例,您可以在這里查看文檔。

此外,現(xiàn)在我們知道 Foo.A 中的動(dòng)態(tài)反射在剪裁和 NativeAOT 下不會(huì)造成任何問(wèn)題,我們可以使用 UnconditionalSuppressMessage 來(lái)抑制警告信息,這樣在構(gòu)建過(guò)程中就不會(huì)再產(chǎn)生任何警告了。

class Foo
{
    readonly Type t = typeof(Bar);
    [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(Bar))]
    [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2080",
        Justification = "The properties of Bar have been preserved by DynamicDependency.")]
    public void A()
    {
        foreach (var prop in t.GetProperties())
        {
            Console.WriteLine(prop);
        }
    }
}

DynamicallyAccessedMembers

有時(shí)我們?cè)噲D動(dòng)態(tài)地訪問(wèn)類型 T 的成員,其中 T 可以是一個(gè)類型參數(shù)或一個(gè) Type 的實(shí)例:

void Foo<T>()
{
    foreach (var prop in typeof(T).GetProperties())
    {
        Console.WriteLine(prop);
    }
}
class Bar
{
    public int X { get; set; }
    public int Y { get; set; }
}

如果我們調(diào)用 Foo<Bar>,很不幸,這在 NativeAOT 下不會(huì)工作。編譯器確實(shí)看到你是用類型參數(shù) Bar 調(diào)用 Foo 的,但在 Foo<T> 的上下文中,編譯器不知道 T 是什么,而且沒(méi)有其他代碼直接使用 Bar 的屬性,所以編譯器不會(huì)為 Bar 的屬性生成代碼。

這里我們可以使用 DynamicallyAccessedMembers 來(lái)告訴編譯器為 T 的所有公共屬性生成代碼:

void Foo<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>()
{
    // ...
}

現(xiàn)在當(dāng)編譯器編譯調(diào)用 Foo<Bar> 時(shí),它知道 T(特別的,這里指 Bar)的所有公共屬性都應(yīng)該被視為依賴。

這個(gè)屬性也可以應(yīng)用在一個(gè) Type 上:

Foo(typeof(Bar));
void Foo([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type t)
{
    foreach (var prop in t.GetProperties())
    {
        Console.WriteLine(prop);
    }
}

甚至在一個(gè) string 上:

Foo("Bar");
void Foo([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] string s)
{
    foreach (var prop in Type.GetType(s).GetProperties())
    {
        Console.WriteLine(prop);
    }
}

所以在這里你可能會(huì)發(fā)現(xiàn)我們有一個(gè)替代方案,用于我們?cè)?nbsp;DynamicDependency 一節(jié)中提到的代碼示例:

class Foo
{
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
    readonly Type t = typeof(Bar);
    public void A()
    {
        foreach (var prop in t.GetProperties())
        {
            Console.WriteLine(prop);
        }
    }
}

順便說(shuō)一句,這也是推薦的方法。

TrimmerRootAssembly

如果你不擁有代碼,但你仍然希望代碼在 NativeAOT 下工作。你可以嘗試使用 TrimmerRootAssembly 來(lái)告訴編譯器將一個(gè)程序集中的所有類型和方法都作為依賴。但請(qǐng)注意,這種方法不適用于泛型實(shí)例化。

<ItemGroup>
    <TrimmerRootAssembly Include="MyAssembly" />
</ItemGroup>

TrimmerRootDescriptor

對(duì)于高級(jí)用戶,他們可能想要控制從一個(gè)程序集中包含什么。在這種情況下,可以指定一個(gè) TrimmerRootDescriptor

<ItemGroup>
    <TrimmerRootDescriptor Include="link.xml" />
</ItemGroup>

TrimmerRootDescriptor 文件的文檔和格式可以在這里找到。

Runtime Directives

對(duì)于泛型實(shí)例化的情況,它們無(wú)法通過(guò) TrimmerRootAssembly 或 TrimmerRootDescriptor 來(lái)解決,這里需要一個(gè)包含 runtime directives 的文件來(lái)告訴編譯器需要編譯的東西。

<ItemGroup>
    <RdXmlFile Include="rd.xml" />
</ItemGroup>

在 rd.xml 中,你可以為你的泛型類型和方法指定實(shí)例化。

rd.xml 文件的文檔和格式可以在這里找到。

這種方法不推薦,但它可以解決你在使用 NativeAOT 時(shí)遇到的一些難題。請(qǐng)?jiān)谑褂?trimmer descriptor 或 runtime directives 之前,總是考慮用 DynamicallyAccessedMembers 和 DynamicDependency 來(lái)注釋你的代碼,使其與剪裁/AOT 兼容。

結(jié)語(yǔ)

NativeAOT 是 .NET 中一個(gè)非常棒和強(qiáng)大的工具。有了 NativeAOT,你可以以可預(yù)測(cè)的性能構(gòu)建你的應(yīng)用,同時(shí)節(jié)省資源(更低的內(nèi)存占用和更小的二進(jìn)制大小)。

它還將 .NET 帶到了不允許 JIT 編譯器的平臺(tái),例如 iOS 和主機(jī)平臺(tái)。此外,它還使 .NET 能夠運(yùn)行在嵌入式設(shè)備甚至裸機(jī)設(shè)備上(例如在 UEFI 上運(yùn)行)。

在使用工具之前了解工具,這樣你會(huì)節(jié)省很多時(shí)間。

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

相關(guān)文章

最新評(píng)論