.NET 8.0 中有哪些新的變化?

2023-11-15 12:00:59

1效能提升

.NET 8在整個堆疊帶來了數千項效能改進 。預設情況下會啟用一種名為動態組態檔引導優化 (PGO) 的新程式碼生成器,它可以根據實際使用情況優化程式碼,並且可以將應用程式的效能提高高達 20%。現在支援的 AVX-512 指令集能夠對 512 位資料向量執行並行操作,這意味著可以在更短的時間內處理更多的資料。原始型別(數位及其他型別)現在實現了新的可格式化和可解析介面,這使它們能夠直接格式化和解析為 UTF-8,而無需任何轉碼開銷。

2.NET Aspire

.NET Aspire 是一個用於使用 .NET 構建彈性、可觀察和可設定的雲原生應用程式的堆疊。它包括一組針對雲原生而增強的精選元件,預設情況下包括遙測、彈性、設定和執行狀況檢查。結合複雜而簡單的本地開發人員體驗,.NET Aspire 可以在第 1 天和第 100 天輕鬆發現、獲取和設定雲原生應用程式的基本依賴項。

點選這裡檢視.NET Aspire的預覽版本。

3.NET 8 容器增強功能 – 更安全、更緊湊、更高效

使用 .NET 比以往更輕鬆、更安全地使用容器打包應用程式。每個 .NET 映像都包含一個非 root 使用者,從而通過單行設定啟用更安全的容器。.NET SDK 工具無需 Dockerfile 即可釋出容器映像,並且預設情況下是非 root 的。由於 .NET 基礎映像更小,因此可以更快地部署容器化應用程式 - 包括我們映像的新實驗變體,這些變體可為本機 AOT 提供真正最小的應用程式大小。選擇使用新的 Chiseled Ubuntu 映像變體進行更多安全強化,以進一步減少攻擊面。使用 Dockerfile 或 SDK 工具,為任何架構構建應用程式和容器映像。

4原生 AoT – 邁向更高密度可持續計算的旅程

無需等待 JIT(即時)編譯器在執行時編譯程式碼。無需部署JIT編譯器和IL程式碼。AOT 應用程式僅部署應用程式所需的程式碼。應用程式現在可以在不允許使用 JIT 編譯器的受限環境中執行。

5人工智慧 – 將 AI 融入您的 .NET 應用程式

生成式人工智慧和大型語言模型正在改變人工智慧領域,使開發人員能夠在其應用程式中建立獨特的人工智慧體驗。.NET 8 可以通過 .NET SDK 中一流的開箱即用 AI 功能以及與多種工具的無縫整合來輕鬆利用 AI。

.NET 8 為該 庫帶來了多項增強功能,以提高其與生成式 AI 工作負載的相容性,例如整合 Tensor Primitives。隨著人工智慧應用程式的興起,新的工具和 SDK 出現了。我們與眾多內部和外部合作伙伴合作,例如Azure OpenAI、Azure Cognitive Search、Milvus、Qdrant和Microsoft Teams,以確保 .NET 開發人員可以通過各自的 SDK 輕鬆存取各種 AI 模型、服務和平臺。此外,開源語意核心SDK 簡化了這些 AI 元件與新的和現有應用程式的整合,以幫助您提供創新的使用者體驗。System.Numerics

現在提供各種範例和參考模板,展示模式和實踐,以便開發人員輕鬆入門:

6Blazor – 使用 .NET 構建全棧 Web 應用程式

.NET 8 中的 Blazor 可以同時使用伺服器和使用者端來處理您的所有 Web UI 需求。這是全棧 Web UI!通過專注於優化頁面載入時間、可延伸性和提升使用者體驗的多項新增強功能,開發人員現在可以在同一應用程式中使用Blazor Server 和 Blazor WebAssembly,在執行時自動將使用者從伺服器轉移到使用者端。得益於新的基於「Jiterpreter」的執行時和新的內建元件,您的 .NET 程式碼在 WebAssembly 上的執行速度顯著加快。作為增強.NET 8 中整體身份驗證、授權和身份管理的一部分,Blazor 現在支援生成完整的基於 Blazor 的身份 UI。

7.NET MAUI – 提升效能、可靠性和開發人員體驗

.NET MAUI 提供單一專案系統和單一程式碼庫來構建 WinUI、Mac Catalyst、iOS 和 Android 應用程式。本機 AOT(實驗性)現在支援針對類似 iOS 的平臺。適用於 .NET MAUI 的新 Visual Studio Code 擴充套件為您提供了開發跨平臺 .NET 移動和桌面應用程式所需的工具。現在支援 Xcode 15 和 Android API 34,允許您瞄準最新版本的 iOS 和 Android。在效能、控制元件和 UI 元素以及特定於平臺的行為方面進行了大量的質量改進,例如桌面互動新增了更好的點選處理、鍵盤偵聽器等。

8 C# 12 功能 – 簡化語法以提高開發人員的工作效率

C# 12 讓您的編碼體驗更加高效和愉快。現在,您可以使用簡單而優雅的語法在任何類和結構中建立主建構函式。不再需要樣板程式碼來初始化您的欄位和屬性。使用簡潔且富有表現力的語法建立陣列、跨度和其他集合型別時會感到高興。對 lambda 表示式中的引數使用新的預設值。不再需要過載或空檢查來處理可選引數。您甚至可以使用usingalias 指令為任何型別新增別名,而不僅僅是命名型別!

8.1集合表示式

在 C# 12 之前,建立集合需要針對不同場景使用不同的語法。初始化所需的與or不同的語法。以下是建立集合的幾種方法:List<int>int[]Span<int>

int[] x1 = new int[] { 1, 2, 3, 4 };
int[] x2 = Array.Empty<int>();
WriteByteArray(new[] { (byte)1, (byte)2, (byte)3 });
List<int> x4 = new() { 1, 2, 3, 4 };
Span<DateTime> dates = stackalloc DateTime[] { GetDate(0), GetDate(1) };
WriteByteSpan(stackalloc[] { (byte)1, (byte)2, (byte)3 });

8.2任何類或結構上的主建構函式

C# 12 擴充套件了主建構函式以適用於所有類和結構,而不僅僅是記錄。主建構函式允許在宣告類時定義建構函式引數:

public class BankAccount(string accountID, string owner)
{
    public string AccountID { get; } = accountID;
    public string Owner { get; } = owner;

    public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
}

主建構函式引數最常見的用途是:

  • 作為 base() 建構函式呼叫的引數。
  • 初始化成員欄位或屬性。
  • 在範例成員中參照建構函式引數。
  • 刪除依賴注入中的樣板。

8.3別名任意型別

別名型別是從程式碼中刪除複雜型別簽名的便捷方法。using從 C# 12 開始,其他型別在別名指令中有效。例如,這些別名在早期版本的 C# 中無效:

using intArray = int[]; // Array types.
using Point = (int x, int y);  // Tuple type
using unsafe ArrayPtr = int*;  // Pointer type (requires "unsafe")

8.4預設 lambda 引數

從 C# 12 開始,您可以在 lambda 表示式中宣告預設引數:

var IncrementBy = (int source, int increment = 1) => source + increment;

Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7

8.5內聯陣列

執行時團隊和其他庫作者使用內聯陣列來提高應用的效能。 內聯陣列使開發人員能夠建立固定大小的 struct 型別陣列。 具有內聯緩衝區的結構應提供類似於不安全的固定大小緩衝區的效能特徵。 你可能不會宣告自己的內聯陣列,但當它們從執行時 API 作為 System.Span<T>System.ReadOnlySpan<T> 物件公開時,你將透明地使用這些陣列。

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private int _element0;
}

它們的用法與任何其他陣列類似:

var buffer = new Buffer();
for (int i = 0; i < 10; i++)
{
    buffer[i] = i;
}

foreach (var i in buffer)
{
    Console.WriteLine(i);
}

區別在於編譯器可以利用有關內聯陣列的已知資訊。 你可能會像使用任何其他陣列一樣使用內聯陣列。 有關如何宣告內聯陣列的詳細資訊,請參閱有關 struct 型別的語言參考。

9.反射改進

.NET 5 中引入了函數指標,但當時未新增對反射的相應支援。 對函數指標使用 typeof 或反射時(例如分別使用 typeof(delegate*<void>()) 或 FieldInfo.FieldType),返回了 IntPtr。 從 .NET 8 開始,將改為返回 System.Type 物件。 此型別提供對函數指標後設資料的存取,包括呼叫約定、返回型別和引數。

新功能目前僅在 CoreCLR 執行時和 MetadataLoadContext 中實現。已將新的 API 新增到 System.Type(例如 IsFunctionPointer)以及 System.Reflection.PropertyInfo、System.Reflection.FieldInfo 和 System.Reflection.ParameterInfo。 以下程式碼演示如何使用一些新 API 進行反射。

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

// ...

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()}");

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;

// 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.
foreach (Type modreq in modifiedType.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers())
{
    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

10.設定繫結源生成器

.NET 8 引入了一個源生成器,用於在 ASP.NET Core 中提供 AOT 和適合剪裁的設定。 該生成器是現有的基於反射的實現的替代方法。

源生成器探測 Configure(TOptions)、Bind 和 Get 呼叫來從中檢索型別資訊。 在專案中啟用生成器後,編譯器將隱式選擇生成的方法,而非預先存在的基於反射的框架實現。

無需更改原始碼即可使用生成器。 AOT Web 應用中預設啟用該生成器。 對於其他專案型別,源生成器預設關閉,但你可通過在專案檔案中將 EnableConfigurationBindingGenerator 屬性設定為 true 來選擇使用它:

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

以下程式碼演示了呼叫繫結器的範例:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

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 MyOptions();
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; }
}

11.針對 Android 應用的 AOT 編譯

為了減小應用大小,面向 Android 的 .NET 和 .NET MAUI 應用在釋出模式下構建時使用分析的預先 (AOT) 編譯模式。 與常規 AOT 編譯相比,分析的 AOT 編譯所影響的方法更少。 .NET 8 引入了 <AndroidStripILAfterAOT> 屬性,你可使用它進一步對 Android 應用進行 AOT 編譯,從而更進一步減少應用大小。

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

預設情況下,將 AndroidStripILAfterAOT 設定為 true 會替代預設的 AndroidEnableProfiledAot 設定,從而允許剪裁已 AOT 編譯的(幾乎)所有方法。 還可通過將兩個屬性都顯式設定為 true 來結合使用分析的 AOT 和 IL 條帶化:

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

12.程式碼分析

.NET 8 包括幾個新的程式碼分析器和修復程式,可幫助驗證是否正確且高效地使用 .NET 庫 API。 下表總結了新的分析器。

規則 ID 類別 說明
CA1856 效能 未在引數上正確應用 ConstantExpectedAttribute 屬性時觸發。
CA1857 效能 當引數使用 ConstantExpectedAttribute 新增批註但提供的引數不是常數時觸發。
CA1858 效能 若要確定字串是否以給定字首開頭,最好呼叫 String.StartsWith,而不是呼叫 String.IndexOf,然後將結果與零進行比較。
CA1859 效能 此規則建議儘可能將特定區域性變數、欄位、屬性、方法引數和方法返回型別從介面或抽象型別升級到具體型別。 使用具體型別可生成更高質量的程式碼。
CA1860 效能 若要確定集合型別是否具有任何元素,最好使用 Length、Count 或 IsEmpty,而不是呼叫 Enumerable.Any
CA1861 效能 重複呼叫時,不會重複使用作為引數傳遞的常數陣列,這意味著每次都會建立一個新陣列。 若要提高效能,請考慮將陣列提取到靜態唯讀欄位。
CA1865-CA1867 效能 對於單字串,char 過載的效能更好。
CA2021 可靠性 Enumerable.Cast(IEnumerable)Enumerable.OfType(IEnumerable) 需要相容的型別才能正常執行。 泛型型別不支援擴大轉換和使用者定義的轉換。
CA1510-CA1513 可維護性 在構造新的異常範例方面,引發幫助程式比 if 塊更簡單、更高效。 這四個分析器是為以下例外情況建立的:ArgumentNullExceptionArgumentExceptionArgumentOutOfRangeExceptionObjectDisposedException

13.Core .NET 庫

13.1時間抽象

新的 TimeProvider 類和 ITimer 介面新增了時間抽象功能,讓你可以在測試方案中模擬時間。 此外,還可以使用時間抽象,通過 Task.DelayTask.WaitAsync 來模擬依賴於時間進度的 Task 操作。 時間抽象支援以下基本時間操作:

  • 檢索本地和 UTC 時間
  • 獲取用於測量效能的時間戳
  • 建立計時器

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

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

// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider : TimeProvider
{
    private TimeZoneInfo _zoneInfo;

    public ZonedTimeProvider(TimeZoneInfo zoneInfo) : base()
    {
        _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;
    }

    public override TimeZoneInfo LocalTimeZone => _zoneInfo;

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

// Create a timer using a time provider.
ITimer timer = timeProvider.CreateTimer(callBack, state, delay, Timeout.InfiniteTimeSpan);

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

var period = GetElapsedTime(providerTimestamp1, providerTimestamp2);

13.2UTF8 改進

如果要啟用將型別的類似字串的表示形式寫出到目標範圍,請在型別上實現新的 IUtf8SpanFormattable 介面。 此新介面與 ISpanFormattable 密切相關,但面向 UTF8 和 Span<byte>,而不是 UTF16 和 Span<char>。

IUtf8SpanFormattable 已在所有基元型別(以及其他)上實現,無論是面向 string、Span<char> 還是 Span<byte>,其共用邏輯完全一致。 它完全支援所有格式(包括新的「B」二進位制說明符)和所有區域性。 這意味著現在可以從 Byte、Complex、Char、DateOnly、DateTime、DateTimeOffset、Decimal、Double、Guid、Half、IPAddress、IPNetwork、Int16、Int32、Int64、Int128、IntPtr、NFloat、SByte、Single、Rune、TimeOnly、TimeSpan、UInt16、UInt32、UInt64、UInt128、UIntPtr 和 Version 直接格式化為 UTF8。

新的 Utf8.TryWrite 方法向現有 MemoryExtensions.TryWrite 方法(基於 UTF16)提供基於 UTF8 的對應方法。 可以使用內插字串語法將複雜表示式直接格式化為 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);

13.3加密

.NET 8 新增了對 SHA-3 雜湊基元的支援。 (目前,具有 OpenSSL 1.1.1 或更高版本和 Windows 11 Build 25324 或更高版本的 Linux 支援 SHA-3。)可在其中使用 SHA-2 的 API 現在提供對 SHA-3 的補充。 對於雜湊,這包括 SHA3_256、SHA3_384 和 SHA3_512;對於 HMAC,這包括 HMACSHA3_256、HMACSHA3_384 和 HMACSHA3_512;對於其中可設定演演算法的雜湊,這包括 HashAlgorithmName.SHA3_256、HashAlgorithmName.SHA3_384 和 HashAlgorithmName.SHA3_512;對於 RSA OAEP 加密,這包括 RSAEncryptionPadding.OaepSHA3_256、RSAEncryptionPadding.OaepSHA3_384 和 RSAEncryptionPadding.OaepSHA3_512。

以下範例演示如何使用 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
{
    // ...
}

13.4基於流的 ZipFile 方法

.NET 8 包含 ZipFile.CreateFromDirectory 的新過載,通過它可以收集目錄中包含的所有檔案並壓縮這些檔案,然後將生成的 zip 檔案儲存到提供的流中。 同樣,通過新的 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) { }
}

參考資料:

https://learn.microsoft.com/zh-cn/dotnet/core/whats-new/dotnet-8

擴充套件連結:

如何使用 Blazor 框架在前端瀏覽器中匯入/匯出 Excel XLSX

如何在.NET電子試算表應用程式中建立流程圖

如何將實時資料顯示在前端電子試算表中