共用方式為


.NET 8 執行環境有什麼新內容

本文介紹了 .NET 8 .NET 執行環境的新功能。

效能改善

.NET 8 包含了程式碼產生與即時編譯(JIT)的改進:

  • Arm64 效能提升
  • SIMD 功能改進
  • 支援 AVX-512 ISA 擴充(參見 Vector512 與 AVX-512
  • 雲端原生改進
  • JIT 吞吐量提升
  • 迴路與一般優化
  • 優化對標記為 ThreadStaticAttribute 欄位的存取。
  • 連續登記器分配。 Arm64 有兩條用於表格向量查找的指令,要求其元組運算元中的所有實體都存在於連續的暫存器中。
  • JIT/NativeAOT 現在可以展開並自動向量化某些與 SIMD 結合的記憶體操作,比如比較、複製和歸零,前提是能在編譯時確定它們的大小。

此外,動態配置檔導向優化(PGO)也已改進,現已預設啟用。 你現在不需要使用 執行時設定選項 來啟用它。 動態 PGO 與分層編譯互相配合,根據在層級 0 期間放置的額外工具進一步優化代碼。

平均而言,動態 PGO 提升效能約 15%。 在一個包含 ~4600 次測試的基準測試套件中,有 23% 的效能提升達到 20% 以上。

Codegen 結構升遷

.NET 8 包含一個新的程式碼生成物理升階優化流程,進一步推廣即時編譯器 (JIT) 提昇結構變數的能力。 這種優化(也稱為 聚合的純量替換)將結構變數的欄位替換為原始變數,JIT 因此能夠推理並更精確地優化。

JIT 已經支援此優化,但有幾項重大限制,包括:

  • 它僅支援四個或更少欄位的結構體。
  • 只有當每個欄位是原始型態,或是簡單結構體包裹原始型態時才支援。

實體宣傳消除了這些限制,解決了許多長期存在的 JIT 問題。

廢棄項目收集

.NET 8 新增了即時調整記憶體限制的功能。 這在雲端服務場景中非常有用,因為需求時有時無。 為了具成本效益,服務應隨著需求波動調整資源消耗的規模。 當服務偵測到需求減少時,可以透過減少記憶體限制來縮小資源消耗。 過去此舉會失敗,因為垃圾回收器(GC)未察覺變更,可能分配超過新限制的記憶體。 有了這個改動,你可以呼叫 RefreshMemoryLimit() API 來更新 GC 的記憶體限制至新值。

有一些限制需要注意:

  • 在 32 位元平台(例如 Windows x86 和 Linux ARM)上,如果還沒有既有的堆積硬性限制,.NET 無法建立新的限制。
  • API 可能會回傳非零狀態碼,表示刷新失敗。 如果縮減過於激進,導致 GC 無法靈活調度,這種情況就可能發生。 此時,請考慮呼叫 GC.Collect(2, GCCollectionMode.Aggressive) 以縮減目前記憶體使用量,然後再嘗試一次。
  • 如果你將記憶體限制擴大到總承包商認為程序啟動時能承受的大小, RefreshMemoryLimit 呼叫會成功,但無法使用超過它感知到的記憶體上限。

以下程式碼片段說明如何呼叫 API。

GC.RefreshMemoryLimit();

你也可以刷新一些與記憶體限制相關的 GC 設定。 以下程式碼片段將堆積的硬性限制設為 100 mebibytes(MiB):

AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();

API 可以拋出 , InvalidOperationException 如果硬極限無效,例如堆積硬極限百分比為負,且硬極限過低。 如果刷新時設定的堆積硬上限(不論是因為新的 AppData 設定還是由容器記憶體限制變更隱含的)低於已經配置的內存,就可能發生這種情況。

行動應用程式的全球化

iOS、tvOS 和 MacCatalyst 上的行動應用程式可選擇採用一種新的 混合 全球化模式,採用較輕量的加護病房套裝。 在混合模式下,全球化資料部分來自 ICU 套件,部分來自 Native API 呼叫。 混合模式服務所有 支援行動裝置的地區。

混合模式最適合無法在不變式全球化模式下運作,以及使用從行動裝置的 ICU 資料中刪減的文化的應用程式。 你也可以用它來載入較小的 ICU 資料檔。 (icudt_hybrid.dat 檔案比預設的 ICU 資料檔 icudt.dat 小 34.5%。)

要使用混合全球化模式,請將 MSBuild 屬性設 HybridGlobalization 為 true:

<PropertyGroup>
  <HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>

有一些限制需要注意:

  • 由於原生 API 的限制,並非所有全球化 API 都支援混合模式。
  • 部分支援的 API 行為不同。

要確認你的申請是否受影響,請參閱 行為差異

由原始碼生成的 COM 互通

.NET 8 包含一個支援與 COM 介面互通的新原始碼產生器。 你可以使用GeneratedComInterfaceAttribute,將介面標記為原始碼產生器的 COM 介面。 原始碼產生器會產生程式碼,使 C# 程式碼能呼叫非受管理程式碼。 它也會產生程式碼,使非管理程式碼能呼叫到 C#。 此生成器整合了 LibraryImportAttribute,你可以在具有 GeneratedComInterfaceAttribute 屬性的函式中使用 LibraryImport 型別作為參數和返回型別。

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
    void DoWork();
}

internal partial class MyNativeLib
{
    [LibraryImport(nameof(MyNativeLib))]
    public static partial void GetComInterface(out IComInterface comInterface);
}

原始碼產生器也支援這個新 GeneratedComClassAttribute 屬性,讓你能把實作該 GeneratedComInterfaceAttribute 屬性介面的型別傳給非管理程式碼。 原始碼產生器會產生暴露 COM 物件所需的程式碼,該物件實作介面並將呼叫轉發給受管理實作。

帶有 GeneratedComInterfaceAttribute 屬性的介面方法支援所有與 LibraryImportAttribute 相同的型別,而 LibraryImportAttribute 現在也支援 GeneratedComInterface 屬性化型別和 GeneratedComClass 屬性化型別。

如果你的 C# 程式碼只是使用 GeneratedComInterface 標注的介面來包裝來自非管理程式碼的 COM 物件,或將 C# 的管理物件包裝以供非管理程式碼使用,那麼你可以利用 Options 屬性中的選項來自訂要產生的程式碼。 這些選項意味著你不需要為那些你知道不會用到的情境寫編排器。

原始碼產生器使用新 StrategyBasedComWrappers 型別來建立和管理 COM 物件包裝器與受管理物件包裝器。 這種新類型負責確保提供預期的 .NET 使用者體驗以支持 COM 互通,同時為進階使用者提供自訂功能。 如果你的應用程式有自己的 COM 類型定義機制,或你需要支援原始碼產生 COM 目前不支援的情境,請考慮用新 StrategyBasedComWrappers 型別來補充你情境中缺少的功能,並讓 COM 型別獲得相同的 .NET 使用者體驗。

如果你用的是 Visual Studio,新的分析器和程式碼修正工具讓你很容易將現有的 COM 互操作程式碼轉換成原始碼產生的互操作。 在包含ComImportAttribute的每個介面旁,一個燈泡提供轉換成源生成互操作的選項。 修正後介面會改成使用該 GeneratedComInterfaceAttribute 屬性。 接著在每個實作介面 GeneratedComInterfaceAttribute 的類別旁邊,提示燈泡會提供一個選項,可將 GeneratedComClassAttribute 屬性新增到該類型中。 一旦你的型別轉換完成,你就可以更新你的 DllImport 方法,使其使用 LibraryImportAttribute

局限性

COM 來源產生器不支援公寓親和性,使用 new 關鍵字來啟動 COM CoClass,以及以下 API:

組態繫結來源產生器

.NET 8 引入了一個原始碼產生器,以提供在 ASP.NET Core 中適合 AOT 及修剪操作的配置。 這個產生器是既有反射式實作的替代方案。

來源產生器會尋找 Configure(TOptions)BindGet 呼叫,以取得型別資訊。 當專案啟用產生器時,編譯器會隱含選擇產生的方法,而非既有的反射式框架實作。

使用產生器不需要更改原始碼。 在 AOT 編譯的網頁應用程式中預設會啟用,且設定 PublishTrimmedtrue (.NET 8+ 應用程式)。 其他專案類型預設關閉原始碼產生器,但你可以在專案檔案中設定 EnableConfigurationBindingGenerator 屬性為 true

<PropertyGroup>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

以下程式碼展示了呼叫綁定器的範例。

public class ConfigBindingSG
{
    static void RunIt(params string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        IConfigurationSection section = builder.Configuration.GetSection("MyOptions");

        // !! Configure call - to be replaced with source-gen'd implementation
        builder.Services.Configure<MyOptions>(section);

        // !! Get call - to be replaced with source-gen'd implementation
        MyOptions? options0 = section.Get<MyOptions>();

        // !! Bind call - to be replaced with source-gen'd implementation
        MyOptions options1 = new();
        section.Bind(options1);

        WebApplication app = builder.Build();
        app.MapGet("/", () => "Hello World!");
        app.Run();
    }

    public class MyOptions
    {
        public int A { get; set; }
        public string S { get; set; }
        public byte[] Data { get; set; }
        public Dictionary<string, string> Values { get; set; }
        public List<MyClass> Values2 { get; set; }
    }

    public class MyClass
    {
        public int SomethingElse { get; set; }
    }
}

Core .NET 程式庫

本節包含下列子主題:

反射

函式指標 在 .NET 5 中被引入,但當時尚未加入對應的反射支援。 例如,當使用 typeof 或在函數指標上進行反射(如 typeof(delegate*<void>())FieldInfo.FieldType)時,會回傳IntPtr。 從 .NET 8 開始,會回傳一個System.Type 物件。 此類型提供函式指標元資料存取,包括呼叫慣例、回傳類型及參數。

備註

函數指標實例,即函數的實體位址,仍然表示為 IntPtr。 只有反射類型改變了。

這項新功能目前僅實作於 CoreCLR 執行環境和 MetadataLoadContext

新增了 API 至 System.Type,例如 IsFunctionPointer,以及至 System.Reflection.PropertyInfoSystem.Reflection.FieldInfoSystem.Reflection.ParameterInfo。 以下程式碼展示了如何使用一些新的 API 來進行反射。

using System;
using System.Reflection;

// Sample class that contains a function pointer field.
public unsafe class UClass
{
    public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}

internal class FunctionPointerReflection
{
    public static void RunIt()
    {
        FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));

        // Obtain the function pointer type from a field.
        Type? fpType = fieldInfo?.FieldType;

        // New methods to determine if a type is a function pointer.
        Console.WriteLine(
        $"IsFunctionPointer: {fpType?.IsFunctionPointer}");
        Console.WriteLine(
            $"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");

        // New methods to obtain the return and parameter types.
        Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");

        if (fpType is not null)
        {
            foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
            {
                Console.WriteLine($"Parameter type: {parameterType}");
            }
        }

        // Access to custom modifiers and calling conventions requires a "modified type".
        Type? modifiedType = fieldInfo?.GetModifiedFieldType();

        // A modified type forwards most members to its underlying type.
        Type? normalType = modifiedType?.UnderlyingSystemType;

        if (modifiedType is not null)
        {
            // New method to obtain the calling conventions.
            foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
            {
                Console.WriteLine($"Calling convention: {callConv}");
            }
        }

        // New method to obtain the custom modifiers.
        Type[]? modifiers =
            modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();

        if (modifiers is not null)
        {
            foreach (Type modreq in modifiers)
            {
                Console.WriteLine($"Required modifier for first parameter: {modreq}");
            }
        }
    }
}

前一個範例產生以下輸出:

IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute

Serialization

.NET 8 中序列化與反序列化功能有許多改進 System.Text.Json 。 例如,你可以 自訂 POCO 中不包含的 JSON 屬性處理方式。

以下章節將介紹其他序列化改進:

欲了解更多關於 JSON 序列化的資訊,請參閱 .NET 中的 JSON 序列化與反序列化

內建對其他類型的支援

串列器內建支援以下額外類型。

  • HalfInt128,以及 UInt128 數值類型。

    Console.WriteLine(JsonSerializer.Serialize(
        [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ]
    ));
    // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
    
  • Memory<T>ReadOnlyMemory<T> 值。 byte 數值會序列化為 Base64 字串,其他類型則序列化為 JSON 陣列。

    JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID"
    JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
    

來源產生器

.NET 8 包含了 System.Text.Json 原始碼產生器的 增強功能,旨在讓 原生 AOT 體驗與 基於反射的序列化器相當。 例如:

  • 原始碼產生器現在支援帶有 requiredinit 屬性的序列化型別。 這兩者都已被反射式序列化支援。

  • 改進原始碼生成程式碼的格式化。

  • JsonSourceGenerationOptionsAttribute功能對等於JsonSerializerOptions。 欲了解更多資訊,請參閱指定選項(來源產生)。

  • 額外的診斷(如 SYSLIB1034SYSLIB1039)。

  • 不要包含被忽略或無法進入的屬性。

  • 支援在任意型別內巢狀JsonSerializerContext宣告。

  • 支援弱型別原始碼生成情境下的編譯器產生的類型或無法直述的類型。 由於編譯器自動產生的型別無法由來源產生器明確指定,System.Text.Json 現在會在執行時進行最近祖先解析。 此解析度決定了最適合序列化該值的超型別。

  • 新型轉換器 JsonStringEnumConverter<TEnum>類型。 現有 JsonStringEnumConverter 的類別在 Native AOT 中不被支援。 你可以用以下方式註解你的列舉類型:

    [JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))]
    public enum MyEnum { Value1, Value2, Value3 }
    
    [JsonSerializable(typeof(MyEnum))]
    public partial class MyContext : JsonSerializerContext { }
    

    欲了解更多資訊,請參閱 「序列化列舉欄位為字串」。

  • JsonConverter.Type 屬性讓你查詢非通用 JsonConverter 實例的類型:

    Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters)
        => converters.Where(converter => converter.Type != null)
                     .ToDictionary(converter => converter.Type!);
    

    該性質是可空的,因為它會對 null 實例回傳 JsonConverterFactory,而對 typeof(T) 實例回傳 JsonConverter<T>

鏈源產生器

JsonSerializerOptions 類別包含一個新 TypeInfoResolverChain 屬性,與現有 TypeInfoResolver 屬性相輔相成。 這些特性用於鏈式源產生器的合約客製化。 新增這個屬性意味著你不必在同一呼叫站指定所有鏈式元件——它們可以在事後再加入。 TypeInfoResolverChain 同時也能讓你反思鏈條或移除其中的元件。 欲了解更多資訊,請參閱 Combine source generators(結合來源產生器)。

此外, JsonSerializerOptions.AddContext<TContext>() 現已過時。 它已被 TypeInfoResolverTypeInfoResolverChain 屬性取代。 更多資訊請參見 SYSLIB0049

介面階層

.NET 8 新增了從介面階層序列化屬性的支援。

以下程式碼展示了一個範例,說明即時實作介面與其基礎介面的屬性都被序列化。

public static void InterfaceHierarchies()
{
    IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
    string json = JsonSerializer.Serialize(value);
    Console.WriteLine(json); // {"Derived":1,"Base":0}
}

public interface IBase
{
    public int Base { get; set; }
}

public interface IDerived : IBase
{
    public int Derived { get; set; }
}

public class DerivedImplement : IDerived
{
    public int Base { get; set; }
    public int Derived { get; set; }
}

命名政策

JsonNamingPolicy 包含了針對 snake_case 屬性(以下劃線分隔)和 kebab-case 屬性(以連字號分隔)名稱轉換的新命名政策。 這些政策的使用方式與現有 JsonNamingPolicy.CamelCase 政策相似。

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }

更多資訊請參閱 「使用內建命名政策」。

唯讀屬性

你現在可以反序列化到唯讀欄位或屬性(也就是沒有 set 附加元件的欄位)。

要在全域範圍內啟用此支持,請設定一個新選項,將 PreferredObjectCreationHandling 設定為 JsonObjectCreationHandling.Populate。 如果相容性是疑慮,你也可以透過將屬性放在 [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] 要填充的特定屬性類型或個別屬性上,來更細緻地啟用此功能。

例如,考慮以下將反序列化成具有兩個唯讀屬性的 CustomerInfo 型別的程式碼。

public static void ReadOnlyProperties()
{
    CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
        { "Names":["John Doe"], "Company":{"Name":"Contoso"} }
        """)!;

    Console.WriteLine(JsonSerializer.Serialize(customer));
}

class CompanyInfo
{
    public required string Name { get; set; }
    public string? PhoneNumber { get; set; }
}

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
    // Both of these properties are read-only.
    public List<string> Names { get; } = new();
    public CompanyInfo Company { get; } = new()
    {
        Name = "N/A",
        PhoneNumber = "N/A"
    };
}

在 .NET 8 之前,輸入值被忽略, Names 且 和 Company 屬性保留預設值。

{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}

現在,輸入值用於反序列化時填充唯讀屬性。

{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}

欲了解更多關於 反序列化填充 行為的資訊,請參見 填充初始化屬性

關閉反射預設

你現在可以預設停用反射式序列器。 這個停用功能有助於避免那些實際上未被使用的反射元件被意外根據依賴,尤其是在經過修剪或使用事先編譯(Native AOT)技術的應用程式中。 若要禁用預設的反射式序列化,必須要求將JsonSerializerOptions參數傳遞至JsonSerializer 序列化與反序列化方法,請在專案檔案中設定JsonSerializerIsReflectionEnabledByDefault MSBuild屬性為false

使用新的 IsReflectionEnabledByDefault API 來檢查功能切換的值。 如果您是基於 System.Text.Json 開發的函式庫作者,您可以依靠這個屬性來配置您的預設值,而不會不小心觸發反射元件的根。

更多資訊請參閱「停用反射預設」。

新 JsonNode API 方法

JsonNode類型和System.Text.Json.Nodes.JsonArray類型包括以下新方法。

public partial class JsonNode
{
    // Creates a deep clone of the current node and all its descendants.
    public JsonNode DeepClone();

    // Returns true if the two nodes are equivalent JSON representations.
    public static bool DeepEquals(JsonNode? node1, JsonNode? node2);

    // Determines the JsonValueKind of the current node.
    public JsonValueKind GetValueKind(JsonSerializerOptions options = null);

    // If node is the value of a property in the parent
    // object, returns its name.
    // Throws InvalidOperationException otherwise.
    public string GetPropertyName();

    // If node is the element of a parent JsonArray,
    // returns its index.
    // Throws InvalidOperationException otherwise.
    public int GetElementIndex();

    // Replaces this instance with a new value,
    // updating the parent object/array accordingly.
    public void ReplaceWith<T>(T value);

    // Asynchronously parses a stream as UTF-8 encoded data
    // representing a single JSON value into a JsonNode.
    public static Task<JsonNode?> ParseAsync(
        Stream utf8Json,
        JsonNodeOptions? nodeOptions = null,
        JsonDocumentOptions documentOptions = default,
        CancellationToken cancellationToken = default);
}

public partial class JsonArray
{
    // Returns an IEnumerable<T> view of the current array.
    public IEnumerable<T> GetValues<T>();
}

非私人會員

你可以利用 JsonIncludeAttributeJsonConstructorAttribute 屬性註解將非公開成員加入指定類型的序列化合約。

public static void NonPublicMembers()
{
    string json = JsonSerializer.Serialize(new MyPoco(42));
    Console.WriteLine(json);
    // {"X":42}

    JsonSerializer.Deserialize<MyPoco>(json);
}

public class MyPoco
{
    [JsonConstructor]
    internal MyPoco(int x) => X = x;

    [JsonInclude]
    internal int X { get; }
}

欲了解更多資訊,請參閱 使用不可變類型及非公開成員與存取器

串流反序列化 API

.NET 8 包含新的 IAsyncEnumerable<T> 串流反序列化擴充方法,例如 GetFromJsonAsAsyncEnumerable。 類似的方法曾存在,例如返回 Task<TResult>HttpClientJsonExtensions.GetFromJsonAsync 新的擴充方法會呼叫串流 API,並回傳 IAsyncEnumerable<T>

以下程式碼展示了如何使用新的擴充方法。

public async static void StreamingDeserialization()
{
    const string RequestUri = "https://api.contoso.com/books";
    using var client = new HttpClient();
    IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);

    await foreach (Book? book in books)
    {
        Console.WriteLine($"Read book '{book?.title}'");
    }
}

public record Book(int id, string title, string author, int publishedYear);

WithAddedModifier 擴充方法

新的 WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) 擴充方法讓你可以輕鬆地對任意 IJsonTypeInfoResolver 實例的序列化契約進行修改。

var options = new JsonSerializerOptions
{
    TypeInfoResolver = MyContext.Default
        .WithAddedModifier(static typeInfo =>
        {
            foreach (JsonPropertyInfo prop in typeInfo.Properties)
            {
                prop.Name = prop.Name.ToUpperInvariant();
            }
        })
};

新的 JsonContent.Create 重載

你現在可以使用安全修剪或源生合約來建立 JsonContent 實例。 新方法包括:

var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);

public record Book(int id, string title, string author, int publishedYear);

[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}

凍結一個 JsonSerializerOptions 實例

以下新方法讓你能控制實例何時 JsonSerializerOptions 被凍結:

  • JsonSerializerOptions.MakeReadOnly()

    此過載已設計為「trim-safe」,因此在選項實例未配置解析器的情況下,會引發例外狀況。

  • JsonSerializerOptions.MakeReadOnly(Boolean)

    如果您傳遞 true 給這個超載版本,當選項實例中缺少反射解析器時,將自動使用預設的反射解析器填入。 此方法有標記 RequiresUnreferenceCode/RequiresDynamicCode ,因此不適合原生AOT應用。

IsReadOnly 屬性可以讓你檢查期權實例是否凍結。

時間抽象

新的 TimeProvider 類別和 ITimer 介面增加了 時間抽象 功能,讓你能在測試場景中模擬時間。 此外,你還可以透過時間抽象來模擬那些依賴時間推進的Task操作,並使用Task.DelayTask.WaitAsync來實現。 時間抽象支援以下重要的時間運算:

  • 取得當地時間與UTC時間
  • 取得一個時間戳記以衡量效能
  • 建立計時器

以下程式碼片段展示了一些使用範例。

// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();

TimerCallback callback = s => ((State)s!).Signal();

// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
    callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);

// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();

TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
    private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;

    public override TimeZoneInfo LocalTimeZone => _zoneInfo;

    public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
        new ZonedTimeProvider(zoneInfo);
}

UTF8 改進

如果你想將類型的字串表示形式寫入目標範圍,請在你的型別上實作新的 IUtf8SpanFormattable 介面。 此新介面與 ISpanFormattable密切相關,但針對 UTF8, Span<byte> 而非 UTF16 與 Span<char>

IUtf8SpanFormattable 已在所有原始型別(以及其他類型)上實作,無論是目標 stringSpan<char>、或 Span<byte>,都採用完全相同的共享邏輯。 它完全支援所有格式(包括新的 「B」二進位指定器)及所有文化。 這表示你現在可以從 ByteComplexCharDateOnlyDateTimeDateTimeOffsetDecimalDoubleGuidHalfIPAddressIPNetworkInt16Int32Int64Int128IntPtrNFloatSByteSingleRuneTimeOnlyTimeSpanUInt16UInt32UInt64UInt128UIntPtrVersion 直接格式化成 UTF8。

Utf8.TryWrite 方法提供了基於 UTF8 的對應方法,取代現有 MemoryExtensions.TryWrite 基於 UTF16 的方法。 你可以使用插值字串語法,直接將複雜表達式格式化為 UTF8 位元組,例如:

static bool FormatHexVersion(
    short major,
    short minor,
    short build,
    short revision,
    Span<byte> utf8Bytes,
    out int bytesWritten) =>
    Utf8.TryWrite(
        utf8Bytes,
        CultureInfo.InvariantCulture,
        $"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
        out bytesWritten);

系統的實作會辨識 IUtf8SpanFormattable 的格式值,並利用它們的實作將其 UTF8 表示直接寫入目標區段。

實作中也採用了新 Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32) 方法,這個方法與其 Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) 對應方法共同支援對目標範圍的編碼與解碼。 如果 span 不夠長以容納結果狀態,方法會回傳 false 而非拋出例外。

處理隨機性的方法

System.RandomSystem.Security.Cryptography.RandomNumberGenerator 型別引入了兩種新的隨機性處理方法。

取得GetItems<T>()

新的 System.Random.GetItems and System.Security.Cryptography.RandomNumberGenerator.GetItems 方法允許你從輸入集中隨機選擇指定數量的項目。 以下範例展示了如何使用System.Random.GetItems<T>()(由Random.Shared屬性提供的實例)隨機將 31 個項目插入到陣列中。 這個例子可以用在「Simon」遊戲中,玩家必須記住一連串彩色按鈕。

private static ReadOnlySpan<Button> s_allButtons = new[]
{
    Button.Red,
    Button.Green,
    Button.Blue,
    Button.Yellow,
};

// ...

Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...

洗牌<T>()

Random.ShuffleRandomNumberGenerator.Shuffle<T>(Span<T>) 方法可以讓你隨機化一個跨度的順序。 這些方法有助於減少機器學習中的訓練偏差(所以第一步不一定是訓練,最後還是測試)。

YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);

IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);

DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);

IDataView predictions = model.Transform(split.TestSet);
// ...

以績效為導向的類型

.NET 8 引入了數種新類型,旨在提升應用程式效能。

  • 新的 System.Collections.Frozen 命名空間包含集合類型 FrozenDictionary<TKey,TValue>FrozenSet<T>。 這些類型一旦建立集合,就不允許更改鍵和值。 此要求允許更快的讀取操作(例如, TryGetValue())。 這些類型對於首次使用時填充,並在長期服務期間持續保存的集合特別有用,例如:

    private static readonly FrozenDictionary<string, bool> s_configurationData =
        LoadConfigurationData().ToFrozenDictionary();
    
    // ...
    if (s_configurationData.TryGetValue(key, out bool setting) && setting)
    {
        Process();
    }
    
  • 像是 MemoryExtensions.IndexOfAny 尋找傳遞集合中任一值的首次出現。 新 System.Buffers.SearchValues<T> 型別設計用於傳遞給此類方法。 相應地,.NET 8 新增了多種方法的重載,例如MemoryExtensions.IndexOfAny,這些重載方法接受新型別的實體。 當你建立一個實例 SearchValues<T> 時,優化後續搜尋所需的所有資料都會在 那個時候推導出,也就是說,工作是在前端完成的。 (此 SearchValues<T> 型別在 .NET 9 中擴充。更多資訊請參見 SearchValues 擴充。)

  • System.Text.CompositeFormat 型別對於優化編譯時未知的格式字串非常有用(例如格式字串是從資源檔案載入時)。 前期會多花點時間進行例如解析字串的工作,但這樣可以避免每次使用時重複執行這些操作。

    private static readonly CompositeFormat s_rangeMessage =
        CompositeFormat.Parse(LoadRangeMessageResource());
    
    // ...
    static string GetMessage(int min, int max) =>
        string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
    
  • System.IO.Hashing.XxHash3System.IO.Hashing.XxHash128類型提供了快速 XXH3 與 XXH128 雜湊演算法的實作。

System.Numerics 與 System.Runtime.Intrinsics

本節涵蓋 與 System.NumericsSystem.Runtime.Intrinsics 命名空間的改進。

  • Vector256<T>Matrix3x2Matrix4x4在 .NET 8 上有改進的硬體加速。 例如,Vector256<T> 在可能的情況下被重新實作為 2x Vector128<T> 內部操作。 這允許在Vector128.IsHardwareAccelerated == trueVector256.IsHardwareAccelerated == false時對某些函數進行部分加速,例如在Arm64上。
  • 硬體內在元件現在會以屬性標註 ConstExpected 。 這確保使用者能知道底層硬體何時預期為常數,因此在非恆定值可能意外影響效能時,能察覺到。
  • Lerp(TSelf, TSelf, TSelf) Lerp API 已被加入 IFloatingPointIeee754<TSelf>,並因此加入 floatSingle)、doubleDouble)和 Half。 此 API 允許在兩個值間進行線性插值,且能有效且正確地執行。

Vector512 與 AVX-512

.NET Core 3.0 擴展了 SIMD 支援,納入了針對 x86/x64 的平台專用硬體內建 API。 .NET 5 新增了對 Arm64 的支援,而 .NET 7 則加入了跨平台硬體的內建功能。 .NET 8 透過引入 Vector512<T> 並支援 Intel Advanced Vector Extensions 512(AVX-512) 指令,進一步強化了 SIMD 支援。

具體來說,.NET 8 包含對 AVX-512 以下主要功能的支持:

  • 512 位元向量運算
  • 額外 16 個 SIMD 暫存器
  • 還有額外指令可用於 128 位元、256 位元及 512 位元向量

如果你有支援該功能的硬體,現在 Vector512.IsHardwareAccelerated 就會回報 true

.NET 8 也在此命名空間下新增了數個平台專屬類別 System.Runtime.Intrinsics.X86

這些類別遵循與其他指令集架構(ISA)相同的大致結構,它們會公開一個 IsSupported 屬性及一個適用於僅限 64 位元程序的指令的巢狀 Avx512F.X64 類別。 此外,每個類別都有一個巢狀 Avx512F.VL 類別,提供對應指令集的向量長度擴充功能。

即使你沒有在程式碼中明確使用 Vector512-specific 或 Avx512F-specific 指令,你仍可能受益於新的 AVX-512 支援。 JIT 在使用 Vector128<T> or Vector256<T>時可隱含利用額外的暫存器和指令。 基底類別函式庫在大多數由Span<T>ReadOnlySpan<T>暴露的運算中,內部使用這些硬體固有功能,並且在許多為原始型別暴露的數學 API 中也是如此。

資料驗證

System.ComponentModel.DataAnnotations 命名空間包含了針對雲端原生服務驗證場景的新資料驗證屬性。 既有 DataAnnotations 的驗證器主要針對典型的使用者介面資料輸入驗證,例如表單上的欄位,而新屬性則設計用來驗證非使用者輸入的資料,例如 設定選項。 除了新增的屬性之外,還新增了 RangeAttribute 類型的屬性。

新增 API Description
RangeAttribute.MinimumIsExclusive
RangeAttribute.MaximumIsExclusive
規定允許範圍內是否包含界限。
System.ComponentModel.DataAnnotations.LengthAttribute 指定字串或集合的下界與上界。 例如,集合 [Length(10, 20)] 中至少需要 10 個元素,最多 20 個元素。
System.ComponentModel.DataAnnotations.Base64StringAttribute 驗證字串是否為有效的 Base64 表示。
System.ComponentModel.DataAnnotations.AllowedValuesAttribute
System.ComponentModel.DataAnnotations.DeniedValuesAttribute
分別指定允許清單和拒絕清單。 例如: [AllowedValues("apple", "banana", "mango")]

Metrics

新的 API 允許你在建立 MeterInstrument 物件時附加鍵值對標籤。 已發表的度量數據彙整器可利用標籤區分彙整後的數值。

var options = new MeterOptions("name")
{
    Version = "version",
    // Attach these tags to the created meter.
    Tags = new TagList()
    {
        { "MeterKey1", "MeterValue1" },
        { "MeterKey2", "MeterValue2" }
    }
};

Meter meter = meterFactory!.Create(options);

Counter<int> counterInstrument = meter.CreateCounter<int>(
    "counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);

這些新 API 包括:

Cryptography

.NET 8 新增了對 SHA-3 雜湊原語的支援。 (目前 SHA-3 已被 Linux 支援,包含 OpenSSL 1.1.1 或更新版本,以及 Windows 11 Build 25324 或更新版本。)原本提供 SHA-2 的 API 現在也提供 SHA-3 功能。 這包括 SHA3_256SHA3_384 以及 SHA3_512 用於雜湊;HMACSHA3_256HMACSHA3_384 以及 HMACSHA3_512 用於 HMAC;HashAlgorithmName.SHA3_256HashAlgorithmName.SHA3_384 以及 HashAlgorithmName.SHA3_512 用於演算法可配置的雜湊;以及 RSAEncryptionPadding.OaepSHA3_256RSAEncryptionPadding.OaepSHA3_384 以及 RSAEncryptionPadding.OaepSHA3_512 用於 RSA OAEP 加密。

以下範例說明如何使用 API,包括 SHA3_256.IsSupported 判斷平台是否支援 SHA-3 的屬性。

// Hashing example
if (SHA3_256.IsSupported)
{
    byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
    // ...
}

// Signing example
if (SHA3_256.IsSupported)
{
     using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
     byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
    // ...
}

目前 SHA-3 支援主要針對加密原語的支援。 較高階的架構和協議初期並不預期能完全支援 SHA-3。 這些協定包括 X.509 憑證、 SignedXml以及 COSE。

網路

支援 HTTPS 代理

直到現在,所有支援 HttpClient 的代理類型都允許「中間人」看到客戶端正在連接哪個網站,即使是 HTTPS URI。 HttpClient 現在支援 HTTPS 代理,能在用戶端與代理之間建立加密通道,讓所有請求都能在完全隱私下處理。

要啟用 HTTPS 代理,請設定 all_proxy 環境變數,或使用 WebProxy 類別來程式控制代理。

Unix: export all_proxy=https://x.x.x.x:3218 Windows: set all_proxy=https://x.x.x.x:3218

你也可以用這個 WebProxy 類別程式控制代理。

基於串流的 ZipFile 方法

.NET 8 新增了這些 ZipFile.CreateFromDirectory 檔案的超載功能,讓你可以收集目錄中所有檔案並壓縮,然後將產生的壓縮檔存入提供的串流中。 同樣地,新的 ZipFile.ExtractToDirectory 超載功能允許你提供包含壓縮檔案的串流,並將其內容解壓到檔案系統中。 以下是新的超載:

namespace System.IO.Compression;

public static partial class ZipFile
{
    public static void CreateFromDirectory(
        string sourceDirectoryName, Stream destination);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory);

    public static void CreateFromDirectory(
        string sourceDirectoryName,
        Stream destination,
        CompressionLevel compressionLevel,
        bool includeBaseDirectory,
    Encoding? entryNameEncoding);

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, bool overwriteFiles) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }

    public static void ExtractToDirectory(
        Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}

當磁碟空間受限時,這些新 API 非常有用,因為它們避免了必須將磁碟作為中間步驟。

擴充函式庫

本節包含下列子主題:

鍵控DI服務

鍵依附注入(DI)服務提供一種利用金鑰註冊與檢索 DI 服務的方式。 透過使用金鑰,你可以規劃如何註冊和使用服務。 以下是一些新的 API:

以下範例說明如何使用鍵狀 DI 服務。

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();

class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
    public object? GetData() => cache.Get("data");
}

class SmallCacheConsumer(IServiceProvider serviceProvider)
{
    public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}

public interface ICache
{
    object Get(string key);
}

public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

欲了解更多資訊,請參見 dotnet/runtime#64427

託管生命週期服務

託管服務現在在應用程式生命週期中擁有更多執行選項。 IHostedService 提供 StartAsyncStopAsync,現在 IHostedLifecycleService 提供以下額外方法:

這些方法分別在現有的切入點之前和之後運行。

以下範例展示了如何使用這些新 API。

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

internal class HostedLifecycleServices
{
    public async static void RunIt()
    {
        IHostBuilder hostBuilder = new HostBuilder();
        hostBuilder.ConfigureServices(services =>
        {
            services.AddHostedService<MyService>();
        });

        using (IHost host = hostBuilder.Build())
        {
            await host.StartAsync();
        }
    }

    public class MyService : IHostedLifecycleService
    {
        public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
        public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
    }
}

更多資訊請參見 dotnet/runtime#86511

選項驗證

來源產生器

為了降低啟動負擔並提升驗證功能,我們引入了一套原始碼產生器,實作驗證邏輯。 以下程式碼展示了範例模型與驗證器類別。

public class FirstModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P1 { get; set; } = string.Empty;

    [Microsoft.Extensions.Options.ValidateObjectMembers(
        typeof(SecondValidatorNoNamespace))]
    public SecondModelNoNamespace? P2 { get; set; }
}

public class SecondModelNoNamespace
{
    [Required]
    [MinLength(5)]
    public string P4 { get; set; } = string.Empty;
}

[OptionsValidator]
public partial class FirstValidatorNoNamespace
    : IValidateOptions<FirstModelNoNamespace>
{
}

[OptionsValidator]
public partial class SecondValidatorNoNamespace
    : IValidateOptions<SecondModelNoNamespace>
{
}

如果你的應用程式使用依賴注入,你可以像以下範例程式碼所示注入驗證。

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
    builder.Configuration.GetSection("some string"));

builder.Services.AddSingleton<
    IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
    IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();

ValidateOptionsResultBuilder 類型

.NET 8 引入了 ValidateOptionsResultBuilder 型別以方便建立 ValidateOptionsResult 物件。 重要的是,這個建構器允許多重錯誤累積。 過去,建立 ValidateOptionsResult 實作 IValidateOptions<TOptions>.Validate(String, TOptions) 所需的物件很困難,有時還會導致多層驗證錯誤。 若有多個錯誤,驗證過程通常會在第一個錯誤處停止。

以下程式碼片段展示了ValidateOptionsResultBuilder的範例使用。

ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");

// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();

// Reset the builder to allow using it in new validation operation.
builder.Clear();

LoggerMessageAttribute 建構子

LoggerMessageAttribute 現在提供額外的建構子重載。 以前,你必須選擇無參數建構子,或是需要所有參數(事件 ID、日誌層級和訊息)的建構子。 新的超載提供了更靈活的參數指定,且程式碼更簡潔。 如果你沒有提供事件 ID,系統會自動產生一個。

public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);

擴展指標

IMeterFactory 介面

你可以在相依注入(DI)容器中註冊新 IMeterFactory 介面,並用它來以隔離方式建立 Meter 物件。

IMeterFactory 註冊至 DI 容器,並採用預設的度量器工廠實作:

// 'services' is the DI IServiceCollection.
services.AddMetrics();

消費者接著可以取得水表工廠,並用它來創建新的 Meter 物件。

IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();

MeterOptions options = new MeterOptions("MeterName")
{
    Version = "version",
};

Meter meter = meterFactory.Create(options);

公量收集器<T> 類

MetricCollector<T> 課程允許你記錄公制測量和時間戳記。 此外,這個類別提供彈性,可選擇使用你所選的時間提供者來精確產生時間戳記。

const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;

var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);

Assert.IsNull(collector.LastMeasurement);

counter.Add(3);

// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);

Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);

System.Numerics.Tensor.TensorPrimites

更新後的 System.Numerics.Tensors NuGet 套件包含新 System.Numerics.Tensors.TensorPrimitives 類型的 API,並新增對張量運算的支援。 張量原語優化了像 AI 和機器學習這類資料密集型工作負載。

像語意搜尋與檢索增強生成(RAG)這類 AI 工作負載,透過用相關資料增強提示詞,擴展大型語言模型(如 ChatGPT)的自然語言能力。 對於這些工作負載,對向量的運算——例如餘 弦相似度 以尋找最相關的資料來回答問題——至關重要。 此 TensorPrimitives 類型提供向量操作的 API。

更多資訊請參閱 《宣布.NET 8 RC 2》部落格文章

支援原生 AOT

原生AOT發佈 選項最早於.NET 7中引入。 用 Native AOT 發佈應用程式,會建立一個完全自包含的應用程式版本,不需要執行時間——所有內容都包含在一個檔案中。 .NET 8 為 Native AOT 發佈帶來了以下改進:

  • 新增對 macOS 上 x64 與 Arm64 架構的支援。

  • 在 Linux 上將原生 AOT 應用程式的大小減少最多 50%。 下表顯示以 Native AOT 發佈的「Hello World」應用程式大小,該應用包含 .NET 7 與 .NET 8 的整個執行環境:

    操作系統 .NET 7 .NET 8
    Linux x64(搭配 -p:StripSymbols=true 3.76 MB 1.84 MB
    Windows x64 2.85 MB 1.77 MB
  • 讓你可以指定優化偏好:大小或速度。 預設情況下,編譯器會選擇快速產生程式碼,同時考慮應用程式的大小。 不過,你可以使用 <OptimizationPreference> MSBuild 屬性來針對其中一種特別優化。 欲了解更多資訊,請參閱優化AOT部署。

針對類似 iOS 的平台提供原生 AOT

.NET 8 開始著手啟用 iOS 類平台的原生 AOT 支援。 你現在可以在以下平台上以原生AOT建置並執行.NET iOS及.NET MAUI應用程式:

  • ios
  • iossimulator
  • maccatalyst
  • tvos
  • tvossimulator

初步測試顯示,使用 Native AOT 而非 Mono 的 .NET iOS 應用程式,磁碟上的應用程式大小會減少約 35%。 .NET MAUI iOS 應用程式的磁碟應用程式大小可減少最多 50%。 此外,啟動時間也更快。 .NET iOS 應用程式的啟動速度大約快 28%,而 .NET MAUI iOS 應用程式的啟動效能比 Mono 快約 50%。 .NET 8 支援仍屬實驗階段,且僅是整體功能的第一步。 欲了解更多資訊,請參閱 .NET 8 .NET MAUI 效能改進部落格文章

原生 AOT 支援作為選擇加入的功能,供應用程式部署使用;Mono 仍然是應用程式開發和部署的預設執行環境。 要在 iOS 裝置上建置並執行帶有原生 AOT 的 .NET MAUI 應用程式,請安裝 dotnet workload install maui .NET MAUI 工作負載並 dotnet new maui -n HelloMaui 建立應用程式。 接著,在專案檔案中將 MSBuild 屬性PublishAot設為 。true

<PropertyGroup>
  <PublishAot>true</PublishAot>
</PropertyGroup>

當你設定所需屬性並執行 dotnet publish 時,應用程式將透過 Native AOT 部署。

dotnet publish -f net8.0-ios -c Release -r ios-arm64  /t:Run

局限性

並非所有 iOS 功能都與原生 AOT 相容。 同樣地,並非所有 iOS 常用的函式庫都與 NativeAOT 相容。 除了現有原生 AOT部署的限制外,以下列表展示了針對類iOS平台時的其他限制:

  • 只有在應用程式部署期間才啟用原生 AOT (dotnet publish)。
  • 管理程式碼除錯僅支援 Mono。
  • 與 .NET MAUI 框架的相容性有限。

Android 應用程式的 AOT 編譯

為了減少應用程式大小,針對 Android 的 .NET 和 .NET MAUI 應用程式在以發佈模式建置時,會使用 以配置的預先編譯(AOT)編譯模式。 具分析的AOT編譯影響的方法比一般AOT編譯少。 .NET 8 引入 <AndroidStripILAfterAOT> 了這個特性,讓你可以選擇加入 Android 應用程式的 AOT 編譯,進一步縮小應用程式大小。

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>

預設情況下,將 AndroidStripILAfterAOT 設定為 true 會覆蓋預設的 AndroidEnableProfiledAot 設定,從而使幾乎所有已經 AOT 編譯的方法都能被修剪。 你也可以同時使用剖面 AOT 和 IL 剝離,方法是明確將兩個屬性設定為 true

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
  <AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>

跨平台建置的 Windows 應用程式

當你在非 Windows 平台上建置針對 Windows 的應用程式時,產生的執行檔會更新任何指定的 Win32 資源——例如應用程式圖示、清單、版本資訊。

過去,應用程式必須在 Windows 上建置才能擁有這些資源。 解決跨建築支援的缺口一直是熱門要求,因為它是影響基礎設施複雜度與資源使用的重要痛點。

另請參閱