共用方式為


適用於 .NET 9 的 .NET 連結庫中的新功能

本文說明適用於 .NET 9 的 .NET 連結庫中的新功能。

Base64Url

Base64 是一種編碼配置,會將任意位元組轉譯成由一組特定 64 個字元組成的文字。 這是傳輸資料的常見方法,而且長期以來一直透過各種方法支援,例如 搭配 Convert.ToBase64StringBase64.DecodeFromUtf8(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32, Boolean)。 不過,它使用的一些字元,使其不理想於在某些情況下使用,否則您可能會想要使用它,例如在查詢字串中。 特別是,組成Base64數據表的64個字元包括 『+』 和 『/』,這兩者在URL中都有自己的意義。 這導致建立Base64Url配置,其類似於Base64,但會使用稍微不同的字元集,使其適合在URL內容中使用。 .NET 9 包含新的 Base64Url 類別,其提供許多實用且優化的方法,以編碼和譯碼 Base64Url 各種數據類型。

下列範例示範如何使用新的 類別。

ReadOnlySpan<byte> bytes = ...;
string encoded = Base64Url.EncodeToString(bytes);

二進位格式化程式

.NET 9 會 BinaryFormatter 從 .NET 運行時間移除。 API 仍然存在,但不論項目類型為何,其實作一律會擲回例外狀況。 如需有關移除和選項的詳細資訊,請參閱 BinaryFormatter 移轉指南

收藏品

.NET 中的集合類型會取得 .NET 9 的下列更新:

具有範圍的集合查閱

在高效能程序代碼中,範圍通常用來避免不必要地配置字串,而且查閱具有類似和 Dictionary<TKey,TValue> 類型的HashSet<T>查閱數據表經常做為快取。 不過,沒有安全且內建的機制可跨範圍對這些集合類型執行查閱。 使用 C# 13 的新功能 allows ref struct 和 .NET 9 中這些集合類型的新功能,現在可以執行這類查閱。

下列範例示範如何使用 Dictionary<TKey,TValue>。GetAlternateLookup

static Dictionary<string, int> CountWords(ReadOnlySpan<char> input)
{
    Dictionary<string, int> wordCounts = new(StringComparer.OrdinalIgnoreCase);
    Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> spanLookup =
        wordCounts.GetAlternateLookup<ReadOnlySpan<char>>();

    foreach (Range wordRange in Regex.EnumerateSplits(input, @"\b\W+"))
    {
        if (wordRange.Start.Value == wordRange.End.Value)
        {
            continue; // Skip empty ranges.
        }
        ReadOnlySpan<char> word = input[wordRange];
        spanLookup[word] = spanLookup.TryGetValue(word, out int count) ? count + 1 : 1;
    }

    return wordCounts;
}

OrderedDictionary<TKey, TValue>

在許多情況下,您可能會想要以維護順序的方式儲存索引鍵/值組(索引鍵/值組的清單),但同時支援索引鍵快速查閱(索引鍵/值組的字典)。 自 .NET 初期以來, OrderedDictionary 此類型已支援此案例,但僅以非泛型方式,且索引鍵和值類型為 object。 .NET 9 引進了長時間要求的 OrderedDictionary<TKey,TValue> 集合,可提供有效率的泛型類型來支持這些案例。

下列程式代碼會使用新的類別。

OrderedDictionary<string, int> d = new()
{
    ["a"] = 1,
    ["b"] = 2,
    ["c"] = 3,
};

d.Add("d", 4);
d.RemoveAt(0);
d.RemoveAt(2);
d.Insert(0, "e", 5);

foreach (KeyValuePair<string, int> entry in d)
{
    Console.WriteLine(entry);
}

// Output:
// [e, 5]
// [b, 2]
// [c, 3]

PriorityQueue.Remove() 方法

.NET 6 引進集合 PriorityQueue<TElement,TPriority> ,提供簡單且快速的陣列堆積實作。 數位堆積通常有一個問題是,它們 不支持優先順序更新,這使得它們禁止在 Dijkstra 演演算法的變化等演算法中使用。

雖然您無法在現有集合中實作有效率的$O(\log n)$ 優先順序更新,但新的 PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) 方法可讓您模擬優先順序更新(雖然在$O(n)$ 時間):

public static void UpdatePriority<TElement, TPriority>(
    this PriorityQueue<TElement, TPriority> queue,
    TElement element,
    TPriority priority
    )
{
    // Scan the heap for entries matching the current element.
    queue.Remove(element, out _, out _);
    // Re-insert the entry with the new priority.
    queue.Enqueue(element, priority);
}

此方法會解除封鎖想要在非一般效能不是封鎖程式的內容中實作圖表演算法的使用者。 (這類內容包括教育和原型設計。例如,以下是使用新 API 之 Dijkstra 演算法的玩具實 作。

ReadOnlySet<T>

通常會想要提供集合的唯讀檢視。 ReadOnlyCollection<T> 可讓您在任意可 IList<T>變動的 周圍建立只讀包裝函式,並 ReadOnlyDictionary<TKey,TValue> 可讓您在任意可 IDictionary<TKey,TValue>變動的 周圍建立只讀包裝函式。 不過,過去版本的 .NET 沒有內建支援使用 ISet<T>執行相同動作。 .NET 9 引進 ReadOnlySet<T> 解決此問題。

新的類別會啟用下列使用模式。

private readonly HashSet<int> _set = [];
private ReadOnlySet<int>? _setWrapper;

public ReadOnlySet<int> Set => _setWrapper ??= new(_set);

元件模型 - TypeDescriptor 修剪支援

System.ComponentModel 包含用於描述元件的新選擇加入修剪器相容 API。 任何應用程式,特別是獨立的修剪應用程式,都可以使用這些新的 API 來協助支援修剪案例。

主要 API 是 TypeDescriptor.RegisterType 類別上的 TypeDescriptor 方法。 這個方法具有 DynamicallyAccessedMembersAttribute 屬性,讓修剪器保留該類型的成員。 您應該為每個類型呼叫這個方法一次,而且通常早點呼叫此方法。

次要 API 有 FromRegisteredType 後綴,例如 TypeDescriptor.GetPropertiesFromRegisteredType(Type)。 與其沒有 FromRegisteredType 後綴的對應專案不同,這些 API 沒有 [RequiresUnreferencedCode][DynamicallyAccessedMembers] 修剪器屬性。 缺少修剪器屬性可協助取用者不再需要:

  • 隱藏修剪警告,這可能會有風險。
  • 將強型別 Type 參數傳播至其他方法,可能很麻煩或不可行。
public static void RunIt()
{
    // The Type from typeof() is passed to a different method.
    // The trimmer doesn't know about ExampleClass anymore
    // and thus there will be warnings when trimming.
    Test(typeof(ExampleClass));
    Console.ReadLine();
}

private static void Test(Type type)
{
    // When publishing self-contained + trimmed,
    // this line produces warnings IL2026 and IL2067.
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(type);

    // When publishing self-contained + trimmed,
    // the property count is 0 here instead of 2.
    Console.WriteLine($"Property count: {properties.Count}");

    // To avoid the warning and ensure reflection
    // can see the properties, register the type:
    TypeDescriptor.RegisterType<ExampleClass>();
    // Get properties from the registered type.
    properties = TypeDescriptor.GetPropertiesFromRegisteredType(type);

    Console.WriteLine($"Property count: {properties.Count}");
}

public class ExampleClass
{
    public string? Property1 { get; set; }
    public int Property2 { get; set; }
}

如需詳細資訊,請參閱 API 提案

密碼學

CryptographicOperations.HashData() 方法

.NET 包含數個哈希函式和相關函式的靜態 「一次性」 實作。 這些 API 包括 SHA256.HashDataHMACSHA256.HashData。 建議使用一次性 API,因為它們可以提供最佳的效能,並減少或排除配置。

如果開發人員想要提供支援哈希的 API,而呼叫端定義要使用的哈希演算法,通常會藉由接受 HashAlgorithmName 自變數來完成。 不過,使用該模式搭配單次 API 需要切換每個可能 HashAlgorithmName ,然後使用適當的方法。 為了解決這個問題,.NET 9 引進 CryptographicOperations.HashData API。 此 API 可讓您透過輸入產生哈希或 HMAC,做為一次性作業,其中所使用的演算法是由 所 HashAlgorithmName決定。

static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
    byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
    ProcessHash(hash);
}

KMAC 演算法

.NET 9 提供 NIST SP-800-185 所指定的 KMAC 演算法。 KECCAK 訊息驗證碼 (KMAC) 是以 KECCAK 為基礎的虛擬隨機函式和索引哈希函式。

下列新類別使用 KMAC 演算法。 使用實例來累積數據來產生 MAC,或使用靜態 HashData 方法進行 單一輸入的單一拍攝

KMAC 可在 Linux 上使用 OpenSSL 3.0 或更新版本,以及 Windows 11 組建 26016 或更新版本。 您可以使用靜態 IsSupported 屬性來判斷平臺是否支援所需的演算法。

if (Kmac128.IsSupported)
{
    byte[] key = GetKmacKey();
    byte[] input = GetInputToMac();
    byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else
{
    // Handle scenario where KMAC isn't available.
}

針對 iOS/tvOS/MacCatalyst 啟用 AES-GCM 和 ChaChaPoly1305 演算法

IsSupported 在 iOS 13+、tvOS 13+和 Mac Catalyst 上執行時,現在 ChaChaPoly1305.IsSupported 會傳回 true。

AesGcm 僅支援 Apple作系統上的 16 位元組 (128 位) 標籤。

X.509 憑證載入

自 .NET Framework 2.0 起,載入憑證的方式一直是 new X509Certificate2(bytes)。 也有其他模式,例如 new X509Certificate2(bytes, password, flags)new X509Certificate2(path)new X509Certificate2(path, password, flags)、 和 X509Certificate2Collection.Import(bytes, password, flags) (及其多載)。

這些方法都使用內容探查來找出輸入是否為它可處理的內容,然後在可以處理時載入它。 對於一些來電者來說,這個策略是非常方便的。 但它也有一些問題:

  • 並非所有檔案格式都適用於每個 OS。
  • 這是通訊協定偏差。
  • 這是安全性問題的來源。

.NET 9 引進了具有「一種方法,一個用途」設計的新 X509CertificateLoader 類別。 在其初始版本中,它只支援建構函式支援的五種格式 X509Certificate2 中的兩種。 這些是適用於所有作系統的兩種格式。

OpenSSL 提供者支援

.NET 8 引進了 OpenSSL 特定 API OpenPrivateKeyFromEngine(String, String)OpenPublicKeyFromEngine(String, String)。 例如,它們能與 OpenSSL ENGINE 元件互動,並使用硬體安全模組(HSM)。

.NET 9 引進 SafeEvpPKeyHandle.OpenKeyFromProvider(String, String),可讓您使用 OpenSSL 提供者,並與 或 tpm2pkcs11提供者互動。

某些散發版本已 移除 ENGINE 支援 ,因為它現在已被取代。

下列代碼段顯示基本用法:

byte[] data = [ /* example data */ ];

// Refer to your provider documentation, for example, https://github.com/tpm2-software/tpm2-openssl/tree/master.
using (SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("tpm2", "handle:0x81000007"))
using (ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle))
{
    byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
    // Do stuff with signature created by TPM.
}

TLS 交握期間有一些效能改善,以及使用 ENGINE 元件的 RSA 私鑰互動的改善。

Windows CNG 虛擬化型安全性

Windows 11 新增了新的 API,可協助使用 虛擬化型安全性 (VBS) 保護 Windows 密鑰。 透過這項新功能,密鑰可以受到保護,不受系統管理層級密鑰竊取攻擊的影響,而對效能、可靠性或規模的影響微乎其微。

.NET 9 已新增相符 CngKeyCreationOptions 旗標。 已新增下列三個旗標:

  • CngKeyCreationOptions.PreferVbs 匹配 NCRYPT_PREFER_VBS_FLAG
  • CngKeyCreationOptions.RequireVbs 匹配 NCRYPT_REQUIRE_VBS_FLAG
  • CngKeyCreationOptions.UsePerBootKey 匹配 NCRYPT_USE_PER_BOOT_KEY_FLAG

下列代碼段示範如何使用其中一個旗標:

using System.Security.Cryptography;

CngKeyCreationParameters cngCreationParams = new()
{
    Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
    KeyCreationOptions = CngKeyCreationOptions.RequireVbs | CngKeyCreationOptions.OverwriteExistingKey,
};

using (CngKey key = CngKey.Create(CngAlgorithm.ECDsaP256, "myKey", cngCreationParams))
using (ECDsaCng ecdsa = new ECDsaCng(key))
{
    // Do stuff with the key.
}

日期和時間 - 新的 TimeSpan.From* 多載

類別TimeSpan提供數From*種方法,可讓您使用 TimeSpan建立 double 物件。 不過,由於 double 是二進位型浮點格式, 固有的不切度可能會導致錯誤。 例如, TimeSpan.FromSeconds(101.832) 可能不精確表示 101 seconds, 832 milliseconds,而是大約 101 seconds, 831.9999999999936335370875895023345947265625 milliseconds表示 。 這種差異造成了頻繁的混淆,而且並不是代表這類數據最有效率的方式。 為了解決這個問題,.NET 9 會新增多載,讓您從整數建立 TimeSpan 物件。 、、FromDaysFromHoursFromMinutesFromSecondsFromMilliseconds都有新的多載FromMicroseconds

下列程式代碼示範呼叫 double 和其中一個新整數多載的範例。

TimeSpan timeSpan1 = TimeSpan.FromSeconds(value: 101.832);
Console.WriteLine($"timeSpan1 = {timeSpan1}");
// timeSpan1 = 00:01:41.8319999

TimeSpan timeSpan2 = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832);
Console.WriteLine($"timeSpan2 = {timeSpan2}");
// timeSpan2 = 00:01:41.8320000

相依性插入 - ActivatorUtilities.CreateInstance 建構函式

的建構函式解析 ActivatorUtilities.CreateInstance 已在 .NET 9 中變更。 先前,根據建構函式的順序和建構函式參數數目而定,可能無法呼叫使用 ActivatorUtilitiesConstructorAttribute 屬性明確標記的建構函式。 .NET 9 中的邏輯已變更,因此一律會呼叫具有 屬性的建構函式。

診斷

Debug.Assert 預設會報告判斷提示條件

Debug.Assert 通常用來協助驗證預期一律為 true 的條件。 失敗通常表示程式代碼中的 Bug。 有許多 多載 Debug.Assert,其中最簡單的只是接受條件:

Debug.Assert(a > 0 && b > 0);

如果條件為 false,判斷提示就會失敗。 然而,從歷史上看,這種斷言是關於什麼條件失敗的任何資訊都無效。 從 .NET 9 開始,如果使用者未明確提供任何訊息,判斷提示將會包含條件的文字表示法。 例如,針對先前的判斷提示範例,而不是收到如下的訊息:

Process terminated. Assertion failed.
   at Program.SomeMethod(Int32 a, Int32 b)

訊息現在會是:

Process terminated. Assertion failed.
a > 0 && b > 0
   at Program.SomeMethod(Int32 a, Int32 b)

先前,您只能在Activity時,將追蹤Activity連結至其他追蹤內容。 .NET 9 的新功能, AddLink(ActivityLink) API 可讓您在 Activity 建立對象之後,將對象連結至其他追蹤內容。 這項變更也會與 OpenTelemetry 規格 一致。

ActivityContext activityContext = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
ActivityLink activityLink = new(activityContext);

Activity activity = new("LinkTest");
activity.AddLink(activityLink);

Metrics.Gauge 檢測

System.Diagnostics.Metrics 現在會根據OpenTelemetry規格提供 Gauge<T> 儀器。 儀器 Gauge 的設計目的是在發生變更時記錄非累加值。 例如,它可以測量背景雜訊等級,其中將多個會議室的值加總會不區分。 Gauge檢測是一種泛型型別,可以記錄任何實值型別,例如 intdoubledecimal

下列範例示範如何使用 Gauge 儀器。

Meter soundMeter = new("MeasurementLibrary.Sound");
Gauge<int> gauge = soundMeter.CreateGauge<int>(
    name: "NoiseLevel",
    unit: "dB", // Decibels.
    description: "Background Noise Level"
    );
gauge.Record(10, new TagList() { { "Room1", "dB" } });

跨程式計量通配符接聽

使用事件來源提供者可以接聽跨進程 System.Diagnostics.Metrics 計量,但在 .NET 9 之前,您必須指定完整計量名稱。 在 .NET 9 中,您可以使用通配符 *來接聽所有計量,這可讓您擷取程式中每個計量的計量。 此外,它新增了依計量前置詞接聽的支援,因此您可以接聽名稱開頭為指定前置詞的所有計量。 例如,指定 MyMeter* 會啟用接聽以 開頭 MyMeter名稱的所有計量。

// The complete meter name is "MyCompany.MyMeter".
var meter = new Meter("MyCompany.MyMeter");
// Create a counter and allow publishing values.
meter.CreateObservableCounter("MyCounter", () => 1);

// Create the listener to use the wildcard character
// to listen to all meters using prefix names.
MyEventListener listener = new MyEventListener();

類別 MyEventListener 的定義如下。

internal class MyEventListener : EventListener
{
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        Console.WriteLine(eventSource.Name);
        if (eventSource.Name == "System.Diagnostics.Metrics")
        {
            // Listen to all meters with names starting with "MyCompany".
            // If using "*", allow listening to all meters.
            EnableEvents(
                eventSource,
                EventLevel.Informational,
                (EventKeywords)0x3,
                new Dictionary<string, string?>() { { "Metrics", "MyCompany*" } }
                );
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        // Ignore other events.
        if (eventData.EventSource.Name != "System.Diagnostics.Metrics" ||
            eventData.EventName == "CollectionStart" ||
            eventData.EventName == "CollectionStop" ||
            eventData.EventName == "InstrumentPublished"
            )
            return;

        Console.WriteLine(eventData.EventName);

        if (eventData.Payload is not null)
        {
            for (int i = 0; i < eventData.Payload.Count; i++)
                Console.WriteLine($"\t{eventData.PayloadNames![i]}: {eventData.Payload[i]}");
        }
    }
}

當您執行程式代碼時,輸出如下所示:

CounterRateValuePublished
        sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
        meterName: MyCompany.MyMeter
        meterVersion:
        instrumentName: MyCounter
        unit:
        tags:
        rate: 0
        value: 1
        instrumentId: 1
CounterRateValuePublished
        sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
        meterName: MyCompany.MyMeter
        meterVersion:
        instrumentName: MyCounter
        unit:
        tags:
        rate: 0
        value: 1
        instrumentId: 1

您也可以使用通配符來接聽計量與 dotnet-counters 等監視工具。

LINQ

已引進新的方法和 CountByAggregateBy 。 這些方法可讓您依索引鍵匯總狀態,而不需要透過 GroupBy配置中繼群組。

CountBy 可讓您快速計算每個索引鍵的頻率。 下列範例會尋找文字字串中最常發生的單字。

string sourceText = """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Sed non risus. Suspendisse lectus tortor, dignissim sit amet, 
    adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";

// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
    .Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(word => word.ToLowerInvariant())
    .CountBy(word => word)
    .MaxBy(pair => pair.Value);

Console.WriteLine(mostFrequentWord.Key); // amet

AggregateBy 可讓您實作更一般用途的工作流程。 下列範例示範如何計算與指定索引鍵相關聯的分數。

(string id, int score)[] data =
    [
        ("0", 42),
        ("1", 5),
        ("2", 4),
        ("1", 10),
        ("0", 25),
    ];

var aggregatedData =
    data.AggregateBy(
        keySelector: entry => entry.id,
        seed: 0,
        (totalScore, curr) => totalScore + curr.score
        );

foreach (var item in aggregatedData)
{
    Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)

Index<TSource>(IEnumerable<TSource>) 可讓您快速擷取可列舉的隱含索引。 您現在可以撰寫程式代碼,例如下列代碼段,以自動為集合中的項目編製索引。

IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
    Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}

記錄來源產生器

C# 12 引進 了主要建構函式,可讓您直接在類別宣告上定義建構函式。 記錄來源產生器現在支援使用具有主要建構函式的類別進行記錄。

public partial class ClassWithPrimaryConstructor(ILogger logger)
{
    [LoggerMessage(0, LogLevel.Debug, "Test.")]
    public partial void Test();
}

其他

在本節中,尋找下列相關信息:

allows ref struct 用於連結庫

C# 13 引進了使用 allows ref struct來限制泛型參數的功能,它會告訴編譯程式和運行時間 ref struct ,可用於該泛型參數。 許多與這個相容的 API 現在已標註。 例如, String.Create 方法具有多載,可讓您直接寫入其記憶體,以表示為範圍來建立 string 。 這個方法具有 TState 從呼叫端傳遞至執行實際寫入之委派的自變數。

上的TStateString.Create別參數現在會以allows ref struct標註:

public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
    where TState : allows ref struct;

此批注可讓您將範圍(或任何其他) ref struct傳遞為此方法的輸入。

下列範例顯示使用這項功能的新 String.ToLowerInvariant() 多載。

public static string ToLowerInvariant(ReadOnlySpan<char> input) =>
    string.Create(span.Length, input, static (stringBuffer, input) => span.ToLowerInvariant(stringBuffer));

SearchValues 擴張

.NET 8 引進了 SearchValues<T> 類型,可提供優化的解決方案,以搜尋範圍內的特定字元或位元組集。 在 .NET 9 中, SearchValues 已擴充為支持搜尋較大字串內的子字串。

下列範例會在字串值內搜尋多個動物名稱,並將索引傳回至找到的第一個動物名稱。

private static readonly SearchValues<string> s_animals =
    SearchValues.Create(["cat", "mouse", "dog", "dolphin"], StringComparison.OrdinalIgnoreCase);

public static int IndexOfAnimal(string text) =>
    text.AsSpan().IndexOfAny(s_animals);

這項新功能具有優化的實作,可利用基礎平臺中的SIMD支援。 它也可讓較高層級的類型優化。 例如, Regex 現在會利用這項功能做為其實作的一部分。

網路

SocketsHttpHandler 預設為 HttpClientFactory

HttpClientFactory 預設會 HttpClient 建立 所 HttpClientHandler支持的物件。 HttpClientHandler 本身是由 SocketsHttpHandler所支援,這更容易設定,包括連接存留期管理。 HttpClientFactory 現在預設會使用 SocketsHttpHandler ,並將其設定為設定其連線存留期的限制,以符合處理站中指定的輪替存留期限制。

System.Net.ServerSentEvents 事件

伺服器傳送的事件 (SSE) 是一種簡單且熱門的通訊協定,可用來將數據從伺服器串流至用戶端。 例如,OpenAI 用來作為其 AI 服務所產生文字串流的一部分。 為了簡化 SSE 的耗用量,新 System.Net.ServerSentEvents 連結庫會提供剖析器,以便輕鬆地內嵌伺服器傳送的事件。

下列程式代碼示範如何使用新的 類別。

Stream responseStream = new MemoryStream();
await foreach (SseItem<string> e in SseParser.Create(responseStream).EnumerateAsync())
{
    Console.WriteLine(e.Data);
}

在 Linux 上使用用戶端憑證繼續 TLS

TLS 繼續 是 TLS 通訊協定的功能,允許繼續先前建立的伺服器會話。 這樣做可避免在 TLS 交握期間往返並節省計算資源。

Linux 上已支援 TLS 繼續,以用於 SslStream 連線,而不需要客戶端憑證。 .NET 9 新增對 TLS 繼續相互驗證 TLS 連線的支援,這在伺服器對伺服器案例中很常見。 此功能會自動啟用。

WebSocket keep-alive ping 和 timeout

開啟 ClientWebSocketOptions 新的 API,並 WebSocketCreationOptions 可讓您選擇在對等未及時回應時傳送 WebSocket Ping 並中止連線。

到目前為止,您可以指定 KeepAliveInterval 來防止連線保持閑置,但沒有內建機制可強制對等回應。

下列範例會每隔 5 秒 Ping 伺服器一次,並在第二秒內未回應時中止連線。

using var cws = new ClientWebSocket();
cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
cws.Options.KeepAliveInterval = TimeSpan.FromSeconds(5);
cws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(1);

await cws.ConnectAsync(uri, httpClient, cancellationToken);

HttpClientFactory 預設不會再記錄標頭值

LogLevel.Trace 根據預設,所 HttpClientFactory 記錄的事件不再包含標頭值。 您可以選擇透過協助程式方法記錄特定標頭 RedactLoggedHeaders 的值。

下列範例會修訂使用者代理程式以外的所有標頭。

services.AddHttpClient("myClient")
    .RedactLoggedHeaders(name => name != "User-Agent");

如需詳細資訊,請參閱 HttpClientFactory 記錄預設會修訂標頭值

反射

保存的元件

在 .NET Core 版本和 .NET 5-8 中,支援建置元件,併發出動態建立類型的反映元數據僅限於可執行 AssemblyBuilder的 。 對於從 .NET Framework 移轉至 .NET 的客戶而言,缺乏 儲存 元件的支援通常是封鎖程式。 .NET 9 會新增類型 PersistedAssemblyBuilder,可用來儲存發出的元件。

若要建立 PersistedAssemblyBuilder 實例,請呼叫其建構函式,並傳遞元件名稱、核心元件、、 System.Private.CoreLib以參考基底運行時間類型,以及選擇性的自定義屬性。 將所有成員發出至元件之後,請呼叫 PersistedAssemblyBuilder.Save(String) 方法來建立具有預設設定的元件。 如果您想要設定進入點或其他選項,您可以呼叫 PersistedAssemblyBuilder.GenerateMetadata 並使用它傳回的元數據來儲存元件。 下列程式代碼顯示建立保存元件並設定進入點的範例。

public void CreateAndSaveAssembly(string assemblyPath)
{
    PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(
        new AssemblyName("MyAssembly"),
        typeof(object).Assembly
        );
    TypeBuilder tb = ab.DefineDynamicModule("MyModule")
        .DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);

    MethodBuilder entryPoint = tb.DefineMethod(
        "Main",
        MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static
        );
    ILGenerator il = entryPoint.GetILGenerator();
    // ...
    il.Emit(OpCodes.Ret);

    tb.CreateType();

    MetadataBuilder metadataBuilder = ab.GenerateMetadata(
        out BlobBuilder ilStream,
        out BlobBuilder fieldData
        );
    PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(
                    imageCharacteristics: Characteristics.ExecutableImage);

    ManagedPEBuilder peBuilder = new ManagedPEBuilder(
                    header: peHeaderBuilder,
                    metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
                    ilStream: ilStream,
                    mappedFieldData: fieldData,
                    entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken)
                    );

    BlobBuilder peBlob = new BlobBuilder();
    peBuilder.Serialize(peBlob);

    using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
    peBlob.WriteContentTo(fileStream);
}

public static void UseAssembly(string assemblyPath)
{
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    Type? type = assembly.GetType("MyType");
    MethodInfo? method = type?.GetMethod("SumMethod");
    Console.WriteLine(method?.Invoke(null, [5, 10]));
}

新的 PersistedAssemblyBuilder 類別包含 PDB 支援。 您可以發出符號資訊,並用它來偵錯產生的元件。 API 具有與 .NET Framework 實作類似的圖形。 如需詳細資訊,請參閱 發出符號併產生 PDB

類型名稱剖析

TypeName 是 ECMA-335 型別名稱的剖析器,可提供與運行時間環境相去甚遠的功能 System.Type ,但與運行時間環境分離。 串行化程式和編譯程式等元件需要剖析和處理類型名稱。 例如,原生 AOT 編譯程式已使用 TypeName切換為 。

新的 TypeName 類別提供:

  • 靜態 ParseTryParse 方法,用於剖析表示為 ReadOnlySpan<char>的輸入。 這兩種方法都接受 類別的 TypeNameParseOptions 實例(選項包),可讓您自定義剖析。

  • NameFullNameAssemblyQualifiedName 屬性,其運作方式與 中的 System.Type對應專案完全相同。

  • 多個屬性和方法,提供名稱本身的其他資訊:

    • IsArrayIsSZArraySZ 代表單一維度、零索引陣列), IsVariableBoundArrayType以及 GetArrayRank 用於處理數位。
    • IsConstructedGenericTypeGetGenericTypeDefinitionGetGenericArguments ,用於使用泛型型別名稱。
    • IsByRefIsPointer ,用於使用指標和Managed參考。
    • GetElementType() 用於使用指標、參考和陣列。
    • IsNestedDeclaringType ,用於使用巢狀類型。
    • AssemblyName,它會透過新 AssemblyNameInfo 類別公開元件名稱資訊。 與相反 AssemblyName地,新類型是不 可變的,而且剖析文化特性名稱不會建立的 CultureInfo實例。

TypeNameAssemblyNameInfo 類型都是不可變的,而且不會提供檢查是否相等的方法(它們不會實IEquatable作)。 比較元件名稱很簡單,但不同的案例只需要比較公開資訊的子集(NameVersionCultureNamePublicKeyOrToken)。

下列代碼段顯示一些範例使用方式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;

internal class RestrictedSerializationBinder
{
    Dictionary<string, Type> AllowList { get; set; }

    RestrictedSerializationBinder(Type[] allowedTypes)
        => AllowList = allowedTypes.ToDictionary(type => type.FullName!);

    Type? GetType(ReadOnlySpan<char> untrustedInput)
    {
        if (!TypeName.TryParse(untrustedInput, out TypeName? parsed))
        {
            throw new InvalidOperationException($"Invalid type name: '{untrustedInput.ToString()}'");
        }

        if (AllowList.TryGetValue(parsed.FullName, out Type? type))
        {
            return type;
        }
        else if (parsed.IsSimple // It's not generic, pointer, reference, or an array.
            && parsed.AssemblyName is not null
            && parsed.AssemblyName.Name == "MyTrustedAssembly"
            )
        {
            return Type.GetType(parsed.AssemblyQualifiedName, throwOnError: true);
        }

        throw new InvalidOperationException($"Not allowed: '{untrustedInput.ToString()}'");
    }
}

新的 API 可從 System.Reflection.Metadata NuGet 套件取得,可與舊版 .NET 版本搭配使用。

正則表達式

[GeneratedRegex] 屬性上的

.NET 7 引進了 Regex 來源產生器和對應的 GeneratedRegexAttribute 屬性。

下列部分方法將會以實作這個 Regex所需的所有程式代碼產生。

[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWord();

C# 13 除了部分方法之外,還支援部分 屬性 ,因此從 .NET 9 開始,您也可以在 [GeneratedRegex(...)] 屬性上使用。

下列部分屬性是上一個範例的對等屬性。

[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWordProperty { get; }

Regex.EnumerateSplits

類別 Regex 提供 Split 方法,類似於 方法的概念 String.Split 。 使用 String.Split時,您會提供一或多個 char 分隔 string 符,而實作會分割這些分隔符上的輸入文字。 使用 Regex.Split,而不是將分隔符指定為 charstring,而是指定為正則表達式模式。

下列範例示範 Regex.Split

foreach (string s in Regex.Split("Hello, world! How are you?", "[aeiou]"))
{
    Console.WriteLine($"Split: \"{s}\"");
}

// Output, split by all English vowels:
// Split: "H"
// Split: "ll"
// Split: ", w"
// Split: "rld! H"
// Split: "w "
// Split: "r"
// Split: " y"
// Split: ""
// Split: "?"

不過, Regex.Split 只接受 string 做為輸入,且不支援以 的形式 ReadOnlySpan<char>提供輸入。 此外,它會輸出完整的分割集做為 string[],這需要配置 string 陣列來保存結果和 string 每個分割的 。 在 .NET 9 中,新 EnumerateSplits 方法會啟用執行相同的作業,但使用範圍型輸入,而且不會產生結果的任何配置。 它會接受 ReadOnlySpan<char> ,並傳回代表結果的 Range 可列舉物件。

下列範例示範 Regex.EnumerateSplits,採用 ReadOnlySpan<char> 作為輸入。

ReadOnlySpan<char> input = "Hello, world! How are you?";
foreach (Range r in Regex.EnumerateSplits(input, "[aeiou]"))
{
    Console.WriteLine($"Split: \"{input[r]}\"");
}

串行化 (System.Text.Json)

縮排選項

JsonSerializerOptions 包含新的屬性,可讓您自定義寫入 JSON 的縮排字元和縮排大小。

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    IndentCharacter = '\t',
    IndentSize = 2,
};

string json = JsonSerializer.Serialize(
    new { Value = 1 },
    options
    );
Console.WriteLine(json);
//{
//                "Value": 1
//}

預設 Web 選項單一

如果您想要使用 ASP.NET Core 用於 Web 應用程式 的預設選項 進行串行化,請使用 new JsonSerializerOptions.Web singleton。

string webJson = JsonSerializer.Serialize(
    new { SomeValue = 42 },
    JsonSerializerOptions.Web // Defaults to camelCase naming policy.
    );
Console.WriteLine(webJson);
// {"someValue":42}

JSON結構輸出器

JSON 經常用來表示方法簽章中的類型,做為遠端過程呼叫配置的一部分。 例如,它用來作為OpenAPI規格的一部分,或做為使用類似OpenAI之 AI 服務呼叫的工具的一部分。 開發人員可以使用 將 .NET 類型串行化和還原串行化為 JSON System.Text.Json。 但是,它們也必須能夠取得 JSON 架構,以描述 .NET 類型的圖形(亦即,描述要串行化的圖形,以及可還原串行化的內容)。 System.Text.Json 現在提供 JsonSchemaExporter 類型,其支持產生代表 .NET 類型的 JSON 架構。

如需詳細資訊,請參閱 JSON 架構匯出工具

尊重可為 Null 的註解

System.Text.Json 現在可辨識屬性的可為 Null 性批註,並可設定為使用 RespectNullableAnnotations 旗標在串行化和還原串行化期間強制執行這些批註。

下列程式代碼示範如何設定 選項:

public static void RunIt()
{
    JsonSerializerOptions options = new() { RespectNullableAnnotations = true };

    // Throws exception: System.Text.Json.JsonException: The property or field
    // 'Title' on type 'Serialization+Book' doesn't allow getting null values.
    // Consider updating its nullability annotation.
    JsonSerializer.Serialize(new Book { Title = null! }, options);

    // Throws exception: System.Text.Json.JsonException: The property or field
    // 'Title' on type 'Serialization+Book' doesn't allow setting null values.
    // Consider updating its nullability annotation.
    JsonSerializer.Deserialize<Book>("""{ "Title" : null }""", options);
}

public class Book
{
    public required string Title { get; set; }
    public string? Author { get; set; }
    public int PublishYear { get; set; }
}

如需詳細資訊,請參閱 尊重可為 Null 的註釋

需要非選擇性建構函式參數

在過去,在使用建構函式型還原串行化時, System.Text.Json 非選擇性建構函式參數會視為選擇性。 您可以使用新 RespectRequiredConstructorParameters 旗標來變更該行為。

下列程式代碼示範如何設定 選項:

JsonSerializerOptions options = new() { RespectRequiredConstructorParameters = true };

// Throws exception: System.Text.Json.JsonException: JSON deserialization
// for type 'Serialization+MyPoco' was missing required properties including: 'Value'.
JsonSerializer.Deserialize<MyPoco>("""{}""", options);

MyPoco 型別的定義如下:

record MyPoco(string Value);

如需詳細資訊,請參閱 非選擇性建構函式參數

排序 JsonObject 屬性

類型 JsonObject 現在會公開排序的字典,例如啟用明確屬性順序作的 API。

JsonObject jObj = new()
{
    ["key1"] = true,
    ["key3"] = 3
};

Console.WriteLine(jObj is IList<KeyValuePair<string, JsonNode?>>); // True.

// Insert a new key-value pair at the correct position.
int key3Pos = jObj.IndexOf("key3") is int i and >= 0 ? i : 0;
jObj.Insert(key3Pos, "key2", "two");

foreach (KeyValuePair<string, JsonNode?> item in jObj)
{
    Console.WriteLine($"{item.Key}: {item.Value}");
}

// Output:
// key1: true
// key2: two
// key3: 3

如需詳細資訊,請參閱 作屬性順序

自定義列舉成員名稱

System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute 屬性可用來針對串行化為字串的類型,自定義個別列舉成員的名稱:

JsonSerializer.Serialize(MyEnum.Value1 | MyEnum.Value2); // "Value1, Custom enum value"

[Flags, JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
    Value1 = 1,
    [JsonStringEnumMemberName("Custom enum value")]
    Value2 = 2,
}

如需詳細資訊,請參閱 自定義列舉成員名稱

串流多個 JSON 檔

System.Text.Json.Utf8JsonReader 現在支援從單一緩衝區或數據流讀取多個空格符分隔的 JSON 檔。 根據預設,如果讀取器偵測到第一個最上層檔尾端的任何非空格符,則會擲回例外狀況。 您可以使用 旗標來變更此行為 AllowMultipleValues

如需詳細資訊,請參閱 讀取多個 JSON 檔

範圍

在高效能程序代碼中,通常會使用範圍來避免不必要地配置字串。 Span<T>ReadOnlySpan<T> 會繼續徹底改變以 .NET 撰寫程式代碼的方式,並新增了在範圍上運作的更多方法。 .NET 9 包含下列範圍相關更新:

檔案協助程式

類別File現在有新的協助程式,可輕鬆地直接寫入和ReadOnlySpan<char>/ReadOnlySpan<byte>檔案。ReadOnlyMemory<char>/ReadOnlyMemory<byte>

下列程式代碼會有效率地將 ReadOnlySpan<char> 寫入檔案。

ReadOnlySpan<char> text = ...;
File.WriteAllText(filePath, text);

StartsWith<T>(ReadOnlySpan<T>, T)新的和 EndsWith<T>(ReadOnlySpan<T>, T) 擴充方法也已針對範圍新增,讓您輕鬆地測試開頭或結尾是否ReadOnlySpan<T>為特定T值。

下列程式代碼會使用這些新的便利 API。

ReadOnlySpan<char> text = "some arbitrary text";
return text.StartsWith('"') && text.EndsWith('"'); // false

params ReadOnlySpan<T> 重載

C# 一律支援將陣列參數標示為 params。 此關鍵字可啟用簡化的呼叫語法。 例如, String.Join(String, String[]) 方法的第二個參數會標示為 params。 您可以使用陣列或個別傳遞值來呼叫此多載:

string result = string.Join(", ", new string[3] { "a", "b", "c" });
string result = string.Join(", ", "a", "b", "c");

在 .NET 9 之前,當您個別傳遞值時,C# 編譯程式會藉由在三個自變數周圍產生隱含數位來發出與第一次呼叫相同的程序代碼。

從 C# 13 開始,您可以搭配任何可透過集合表達式建構的自變數使用 params ,包括 spans (Span<T>ReadOnlySpan<T>)。 這對可用性和效能很有説明。 C# 編譯程式可以將自變數儲存在堆疊上、將範圍包裝在它們周圍,然後將它傳遞至方法,以避免產生其他結果的隱含數位配置。

.NET 9 包含超過 60 個 params ReadOnlySpan<T> 具有 參數的方法。 有些是全新的多載,有些是現有的方法,已採用 ReadOnlySpan<T> ,但現在有標示為 params的參數。 淨效果是,如果您升級至 .NET 9 並重新編譯程序代碼,您將會看到效能改善,而不會進行任何程式碼變更。 這是因為編譯程式偏好系結至以範圍為基礎的多載,而不是以陣列為基礎的多載。

例如, String.Join 現在包含下列多載,其會實作新的模式: String.Join(String, ReadOnlySpan<String>)

現在,會進行類似 string.Join(", ", "a", "b", "c") 的呼叫,而不需要配置陣列以傳入 "a""b""c" 自變數。

列舉 ReadOnlySpan<char>。Split() 區段

string.Split 是一種方便的方法,可快速分割具有一或多個提供的分隔符的字串。 不過,對於著重於效能的程式代碼而言,的配置配置檔 string.Split 可能令人禁止,因為它會為每個剖析的元件配置一個字串,併 string[] 為其全部儲存。 它也無法使用範圍,因此如果您有 ReadOnlySpan<char>,則當您將它轉換成字串時,系統會強制配置另一個字串,以在字串上呼叫 string.Split 它。

在 .NET 8 中,已針對 引進Split一組 SplitAnyReadOnlySpan<char> 方法。 這些方法不會傳回新的 string[],而是接受每個元件的周框索引寫入目的地 Span<Range> 。 這可讓作業完全無配置。 當範圍數目同時為已知和小型時,這些方法就適合使用。

在 .NET 9 中,已新增 和 Split 的新多載SplitAny,以允許以ReadOnlySpan<T>未知的區段數目累加剖析 。 新的方法可透過每個區段來列舉,其表示方式 Range 類似 ,可用來分割成原始範圍。

public static bool ListContainsItem(ReadOnlySpan<char> span, string item)
{
    foreach (Range segment in span.Split(','))
    {
        if (span[segment].SequenceEquals(item))
        {
            return true;
        }
    }

    return false;
}

System.Formats 格式

物件封入數據流 TarEntry 中的數據位置或位移現在是公用屬性。 TarEntry.DataOffset 會傳回專案封存數據流中專案第一個數據位元組所在的位置。 項目的數據會封裝在您可以透過 TarEntry.DataStream存取的子數據流中,這會隱藏相對於封存數據流之數據的實際位置。 這足以讓大部分的使用者使用,但如果您需要更多彈性,並想要知道數據在封存數據流中的實際起始位置,新的 TarEntry.DataOffset API 可讓您輕鬆地支援與非常大型 TAR 檔案並行存取等功能。

// Create stream for tar ball data in Azure Blob Storage.
BlobClient blobClient = new(connectionString, blobContainerName, blobName);
Stream blobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);

// Create TarReader for the stream and get a TarEntry.
TarReader tarReader = new(blobClientStream);
System.Formats.Tar.TarEntry? tarEntry = await tarReader.GetNextEntryAsync();

if (tarEntry is null)
    return;

// Get position of TarEntry data in blob stream.
long entryOffsetInBlobStream = tarEntry.DataOffset;
long entryLength = tarEntry.Length;

// Create a separate stream.
Stream newBlobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
newBlobClientStream.Seek(entryOffsetInBlobStream, SeekOrigin.Begin);

// Read tar ball content from separate BlobClient stream.
byte[] bytes = new byte[entryLength];
await newBlobClientStream.ReadExactlyAsync(bytes, 0, (int)entryLength);

System.Guid

NewGuid()Guid 根據 RFC 9562 中的 UUID 第 4 版規格,建立填滿大部分的密碼 編譯安全隨機數據。 相同的 RFC 也會定義其他版本,包括第 7 版,其「具有衍生自廣泛實作且知名的 Unix Epoch 時間戳來源的時序值欄位」。 換句話說,大部分的數據仍然是隨機的,但有些數據會根據時間戳保留給數據,這可讓這些值有自然排序順序。 在 .NET 9 中,您可以透過新的 GuidGuid.CreateVersion7() 方法,根據第 7 版建立 Guid.CreateVersion7(DateTimeOffset) 。 您也可以使用新的 Version 屬性來擷取 Guid 物件的版本欄位。

System.IO

使用 zlib-ng 壓縮

System.IO.CompressionZipArchiveDeflateStreamGZipStreamZLibStream功能主要以 zlib 連結庫為基礎。 從 .NET 9 開始,這些功能全都使用 zlib-ng,此連結庫可在更廣泛的作系統和硬體數位中產生更一致且更有效率的處理。

ZLib 和 Brotli 壓縮選項

ZLibCompressionOptionsBrotliCompressionOptions 是用於設定演算法特定壓縮層級和策略的新類型(Default、、FilteredHuffmanOnly、、 RunLengthEncodingFixed)。 這些類型適用於想要比唯一現有的選項 <System.IO.Compression.CompressionLevel> 更微調設定的使用者。

未來可能會擴充新的壓縮選項類型。

下列代碼段顯示一些範例使用方式:

private MemoryStream CompressStream(Stream uncompressedStream)
{
    MemoryStream compressorOutput = new();
    using ZLibStream compressionStream = new(
        compressorOutput,
        new ZLibCompressionOptions()
        {
            CompressionLevel = 6,
            CompressionStrategy = ZLibCompressionStrategy.HuffmanOnly
        }
        );
    uncompressedStream.CopyTo(compressionStream);
    compressionStream.Flush();

    return compressorOutput;
}

XPS 虛擬印表機中的 XPS 檔

由於不支持處理 System.IO.Packaging 檔案,所以先前無法使用連結庫開啟來自 V4 XPS 虛擬印表機的 XPS 檔。 此差距已在 .NET 9 中解決。

System.Numerics

BigInteger 上限

BigInteger 支援表示基本上任意長度的整數值。 不過,實際上,長度受限於基礎計算機的限制,例如可用的記憶體,或計算給定表達式所需的時間。 此外,有一些 API 在給定的輸入導致值太大時失敗。 由於這些限制,.NET 9 會強制執行 的最大長度 BigInteger,也就是它不能包含不超過 (2^31) - 1 (大約 21.4 億個) 位。 這類數位代表幾乎 256 MB 的配置,且包含大約 6.465 億位數。 這項新限制可確保公開的所有 API 都表現良好且一致,同時仍允許遠遠超出大部分使用案例的數位。

BigMul 蜜蜂屬

BigMul 是產生兩個數位完整乘積的作業。 .NET 9 會在 、BigMulintlonguint新增專用 ulong API,其傳回類型是比參數類型更大的下一個整數類型

新的 API 如下:

向量轉換 API

.NET 9 新增專用延伸模組 API,以在 、、Vector2Vector3Vector4Quaternion之間Plane轉換。

新的 API 如下所示:

對於相同大小的轉換,例如 、Vector4、 和 Quaternion之間Plane,這些轉換都是零成本。 同樣可以說是縮小轉換,例如從 Vector4Vector2Vector3。 為了擴大轉換,例如從 Vector2Vector3Vector4,有一般 API 會將新元素初始化為 0,而 Unsafe 後置 API 會將這些新元素保留為未定義,因此成本可能為零。

向量建立 API

針對、 、 和公開的新 Create API Vector會公開針對命名空間中Vector2公開的硬體向量類型公開的對等 API。Vector3Vector4System.Runtime.Intrinsics

如需新 API 的詳細資訊,請參閱:

這些 API 主要是為了方便和整體一致性。NET 的 SIMD 加速類型。

其他加速

命名空間中的System.Numerics許多類型都已改善其他效能,包括、BigIntegerVector2Vector3Vector4QuaternionPlane

在某些情況下,這會導致核心 API 加速 2-5 倍的速度,包括 Matrix4x4 乘法、從一系列的頂點建立 PlaneQuaternion 串連,以及計算 的 Vector3交叉乘積。

API 也有不斷折疊支援 SinCos ,可同時計算 Sin(x)Cos(x) 在單一呼叫中,使其更有效率。

適用於 AI 的 Tensors

張量是人工智慧 (AI) 的基石數據結構。 它們通常可視為多維度陣列。

Tensors 可用來:

  • 表示和編碼數據,例如文字序列(標記)、影像、視訊和音訊。
  • 有效率地作更高維度的數據。
  • 有效率地對較高維度數據套用計算。
  • 儲存權數資訊和中繼計算(在類神經網路中)。

若要使用 .NET tensor API,請安裝 System.Numerics.Tensors NuGet 套件。

新的 Tensor<T> 類型

新的 Tensor<T> 類型會擴充 .NET 連結庫和運行時間的 AI 功能。 此類型:

  • 盡可能使用零份與 AI 連結庫提供有效率的 Interop,例如 ML.NET、TorchSharp 和 ONNX Runtime。
  • 建立在 TensorPrimitives 之上,以進行有效率的數學運算。
  • 藉由提供索引編製和切割作業,以輕鬆且有效率地作數據。
  • 不是現有 AI 和機器學習連結庫的替代專案。 相反地,它旨在提供一組常見的 API 來減少程式代碼重複和相依性,並使用最新的運行時間功能來達到更好的效能。

下列程式代碼顯示新類型隨附 Tensor<T> 的一些 API。

// Create a tensor (1 x 3).
Tensor<int> t0 = Tensor.Create([1, 2, 3], [1, 3]); // [[1, 2, 3]]

// Reshape tensor (3 x 1).
Tensor<int> t1 = t0.Reshape(3, 1); // [[1], [2], [3]]

// Slice tensor (2 x 1).
Tensor<int> t2 = t1.Slice(1.., ..); // [[2], [3]]

// Broadcast tensor (3 x 1) -> (3 x 3).
// [
//  [ 1, 1, 1],
//  [ 2, 2, 2],
//  [ 3, 3, 3]
// ]
var t3 = Tensor.Broadcast<int>(t1, [3, 3]);

// Math operations.
var t4 = Tensor.Add(t0, 1); // [[2, 3, 4]]
var t5 = Tensor.Add(t0.AsReadOnlyTensorSpan(), t0); // [[2, 4, 6]]
var t6 = Tensor.Subtract(t0, 1); // [[0, 1, 2]]
var t7 = Tensor.Subtract(t0.AsReadOnlyTensorSpan(), t0); // [[0, 0, 0]]
var t8 = Tensor.Multiply(t0, 2); // [[2, 4, 6]]
var t9 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t0); // [[1, 4, 9]]
var t10 = Tensor.Divide(t0, 2); // [[0.5, 1, 1.5]]
var t11 = Tensor.Divide(t0.AsReadOnlyTensorSpan(), t0); // [[1, 1, 1]]

備註

此 API 標示為 .NET 9 的 實驗性

TensorPrimitives

連結 System.Numerics.Tensors 庫包含 類別 TensorPrimitives ,提供靜態方法,以在值範圍上執行數值運算。 在 .NET 9 中,所公開 TensorPrimitives 的方法範圍已大幅擴大,從 40 個 (在 .NET 8 中) 成長到近 200 個多載。 介面區包含來自和 MathMathF類型的熟悉數值運算。 它也包含泛型數學介面,例如 INumber<TSelf>,除了處理個別值,而是處理值範圍。 許多作業也已透過適用於 .NET 9 的SIMD優化實作加速。

TensorPrimitives 現在會針對任何實作特定介面的類型 T 公開泛型多載。 (.NET 8 版本只包含用來作值範圍的float多載。例如,新的CosineSimilarity<T>(ReadOnlySpan<T>, ReadOnlySpan<T>)多載會對實作 之其他任何型float別的double兩個向量、Half、 或 IRootFunctions<TSelf> 值執行餘弦相似性。

比較兩個類型 float 向量上的餘弦相似度運算精確度與 double

ReadOnlySpan<float> vector1 = [1, 2, 3];
ReadOnlySpan<float> vector2 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector1, vector2));
// Prints 0.9746318

ReadOnlySpan<double> vector3 = [1, 2, 3];
ReadOnlySpan<double> vector4 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector3, vector4));
// Prints 0.9746318461970762

線程

線程 API 包括逐一查看工作的改善、針對優先通道,其可以排序其元素,而不是先出先出 (FIFO),以及 Interlocked.CompareExchange 更多類型。

Task.WhenEach

已新增各種實用的新 API,以便處理 Task<TResult> 物件。 新的 Task.WhenEach 方法可讓您在使用 語句完成 await foreach 時逐一查看工作。 您不再需要對一組工作重複呼叫 Task.WaitAny 之類的動作,以挑選下一個完成的工作。

下列程式代碼會進行多個 HttpClient 呼叫,並在結果完成時運作。

using HttpClient http = new();

Task<string> dotnet = http.GetStringAsync("http://dot.net");
Task<string> bing = http.GetStringAsync("http://www.bing.com");
Task<string> ms = http.GetStringAsync("http://microsoft.com");

await foreach (Task<string> t in Task.WhenEach(bing, dotnet, ms))
{
    Console.WriteLine(t.Result);
}

已設定未系結通道的優先順序

命名空間 System.Threading.Channels 可讓您使用 CreateBoundedCreateUnbounded 方法來建立先進先出 (FIFO) 通道。 使用 FIFO 通道時,元素會依寫入通道的順序從通道讀取。 在 .NET 9 中 CreateUnboundedPrioritized ,已新增 方法,它會排序從通道讀取的下一個元素是視為最重要的專案,根據 Comparer<T>.Default 或 自定義 IComparer<T>

下列範例會使用新的 方法來建立通道,以依序輸出數位 1 到 5,即使它們以不同的順序寫入通道也一樣。

Channel<int> c = Channel.CreateUnboundedPrioritized<int>();

await c.Writer.WriteAsync(1);
await c.Writer.WriteAsync(5);
await c.Writer.WriteAsync(2);
await c.Writer.WriteAsync(4);
await c.Writer.WriteAsync(3);
c.Writer.Complete();

while (await c.Reader.WaitToReadAsync())
{
    while (c.Reader.TryRead(out int item))
    {
        Console.Write($"{item} ");
    }
}

// Output: 1 2 3 4 5

Interlocked.CompareExchange 適用於更多類型

在舊版 .NET 中,Interlocked.ExchangeInterlocked.CompareExchange具有使用 intuintlongulongnintnuintfloatdoubleobject的多載,以及使用任何參考型T別的泛型多載。 在 .NET 9 中,有新的多載可不可部分完成地使用 bytesbyteshortushort。 此外,已移除泛型和Interlocked.Exchange<T>多載上的Interlocked.CompareExchange<T>泛型條件約束,因此這些方法不再受限於只使用參考型別。 它們現在可以使用任何基本類型,其中包含上述所有類型以及 boolchar以及任何 enum 類型。