.NET 9 的新功能
了解 .NET 9 中的新功能,並找到更多文件的連結。
.NET 9 是 .NET 8 的繼承者,特別關注雲端原生應用程式和效能。 它將作為標準期間支援 (STS) 版本支援 18 個月。 您可以在此處下載 .NET 9。
新的 .NET 9 工程小組在 GitHub Discussions 上張貼 .NET 9 預覽更新。 這是提問和提供有關發行版本意見反應的好地方。
本文已針對 .NET 9 Preview 2 更新。 以下區段介紹了對 .NET 9 中核心 .NET 程式庫的更新。
.NET 執行階段
序列化
System.Text.Json .NET 9 中提供了序列化 JSON 的新選項和新的 singleton,讓使用 web 預設值進行序列化變得更容易。
縮排選項
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 應用程式的預設選項進行序列化,請使用新 JsonSerializerOptions.Web singleton。
string webJson = JsonSerializer.Serialize(
new { SomeValue = 42 },
JsonSerializerOptions.Web // Defaults to camelCase naming policy.
);
Console.WriteLine(webJson);
// {"someValue":42}
LINQ
引入了新的 CountBy 方法和 AggregateBy。 這些方法可以依索引鍵彙總狀態,而無需透過 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}");
}
集合
PriorityQueue<TElement,TPriority>命名空間中的System.Collections.Generic集合類型包含新的Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>)方法,可用來更新佇列中專案的優先順序。
PriorityQueue.Remove() 方法
.NET 6 引入了 PriorityQueue<TElement,TPriority> 集合,它提供了簡單快速的陣列堆積實作。 陣列堆積的一個普遍問題是,它們不支援優先順序更新,這使得它們無法用於 Dijkstra 演算法的變化等演算法。
雖然不可能在現有集合中實作有效的 $O(\log n)$ 優先順序更新,但新 PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) 方法可以模擬優先順序更新 (儘管時間為 $O(n)$ time):
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);
}
此方法可以解除鎖定那些希望在漸近效能不是封鎖因素之環境中實作圖表演算法的使用者。 (這些內容包括教育和原型設計。)例如,這裡有 Dijkstra 的演算法之玩具實作,它使用了新 API。
密碼編譯
對於加密,.NET 9 在 CryptographicOperations 類型上新增了新的單次雜湊方法。 它還新增了使用 KMAC 演算法的新類別。
CryptographicOperations.HashData() 方法
.NET 包括雜湊函式和相關函式的幾個靜態「單次」實作。 這些 API 包括 SHA256.HashData 和 HMACSHA256.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 可在具有 OpenSSL 3.0 或更新版本的 Linux 以及 Windows 11 Build 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.
}
反映
在 .NET Core 版本和 .NET 5-8 中,對為動態建立的類型建置組件和發出反映中繼資料的支援僅限於可執行的 AssemblyBuilder。 對於從 .NET Framework 移轉至 .NET 的客戶來說,缺乏對儲存組件的支援往往是障礙。 .NET 9 向 AssemblyBuilder 新增公用 API 以儲存已發出的組件。
新的持續 AssemblyBuilder 實作與執行階段和平台無關。 若要建立持續 AssemblyBuilder
執行個體,請使用新的 AssemblyBuilder.DefinePersistedAssembly API。 現有 AssemblyBuilder.DefineDynamicAssembly API 接受組件名稱和選擇性自訂屬性。 若要使用新 API,請傳遞用於參考基礎執行階段類型的核心組件 System.Private.CoreLib
。 AssemblyBuilderAccess 沒有選項。 目前,持續 AssemblyBuilder
實作只支援儲存,而不支援執行。 建立持續 AssemblyBuilder
的執行個體後,定義模組、類型、方法或列舉、寫入 IL 以及所有其他使用方式的後續步驟保持不變。 這表示您可以依原樣使用現有 System.Reflection.Emit 程式碼來儲存組件。 下列程式碼為範例。
public void CreateAndSaveAssembly(string assemblyPath)
{
AssemblyBuilder ab = AssemblyBuilder.DefinePersistedAssembly(
new AssemblyName("MyAssembly"),
typeof(object).Assembly
);
TypeBuilder tb = ab.DefineDynamicModule("MyModule")
.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder mb = tb.DefineMethod(
"SumMethod",
MethodAttributes.Public | MethodAttributes.Static,
typeof(int), [typeof(int), typeof(int)]
);
ILGenerator il = mb.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
tb.CreateType();
ab.Save(assemblyPath); // or could save to a Stream
}
public 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]));
}
效能
.NET 9 包含 64 位 JIT 編譯程式的增強功能,旨在改善應用程式效能。 這些編譯程式增強功能包括:
Arm64 向量化 是運行時間的另一個新功能。
迴圈優化
改善迴圈的程式代碼產生是 .NET 9 的優先順序,而64位編譯程式具有稱為 「輔助變數」(IV)擴充的新優化功能。
IV 是變數,其值會隨著包含迴圈逐一查看而變更。 在下列 for
迴圈中, i
是IV: for (int i = 0; i < 10; i++)
。 如果編譯程式可以分析 IV 值在其迴圈反覆專案上演進的方式,它可以為相關運算式產生更高效能的程式代碼。
請考慮下列逐一查看陣列的範例:
static int Sum(int[] arr)
{
int sum = 0;
for (int i = 0; i < arr.Length; i++)
{
sum += arr[i];
}
return sum;
}
索引變數 i
的大小為 4 個字節。 在元件層級,64 位緩存器通常用來保存 x64 上的陣列索引,而在舊版 .NET 中,編譯程式會產生程式代碼,以零擴充 i
至 8 個字節供數位記憶體取,但會繼續視為 i
其他地方的 4 位元組整數。 不過,擴充 i
至8個字節需要 x64 的額外指令。 隨著IV擴大,64位 JIT 編譯程式現在會在整個迴圈中擴大 i
為8個字節,省略零延伸。 在陣列上迴圈是很常見的,而且此指令移除的優點會快速加總。
原生 AOT 的內嵌改善
的其中一個。NET 針對 64 位 JIT 編譯程式內嵌的目標,是移除阻止方法盡可能內嵌的許多限制。 .NET 9 可讓您內嵌 Windows x64、Linux x64 和 Linux Arm64 上線程本機靜態的存取。
針對 static
類別成員,類別的所有實例中只有一個成員實例存在,而該實例會「共用」成員。 如果成員的值對每個線程而言是唯一 static
的,使該值線程區域可以改善效能,因為它不需要並行基本類型,就能從其包含的線程安全地存取 static
成員。
先前,存取原生 AOT 編譯程式中的線程本機靜態時,需要 64 位 JIT 編譯程式發出對運行時間的呼叫,以取得線程本機記憶體的基位址。 現在,編譯程式可以內嵌這些呼叫,因此存取此數據的指示要少得多。
PGO 改善:類型檢查和轉換
根據預設,.NET 8 已啟用動態配置檔引導優化 (PGO)。 NET 9 擴充 64 位 JIT 編譯程式的 PGO 實作,以分析更多程式代碼模式。 啟用階層式編譯時,64 位 JIT 編譯程式已將檢測插入程式,以分析其行為。 使用優化重新編譯時,編譯程式會利用它在運行時間所建置的配置檔,做出您程式目前執行的特定決策。 在 .NET 9 中,64 位 JIT 編譯程式會使用 PGO 數據來改善類型檢查的效能。
判斷物件的類型需要呼叫運行時間,這會產生效能損失。 需要檢查對象的類型時,64 位 JIT 編譯程式會為了正確性發出此呼叫(編譯程式通常無法排除任何可能性,即使它們看起來不可行)。 不過,如果 PGO 數據建議物件可能是特定類型,則 64 位 JIT 編譯程式現在會 發出快速路徑 ,以廉價檢查該類型,並在必要時才回到呼叫運行時間的慢速路徑。
.NET 連結庫中的Arm64向量化
新的 EncodeToUtf8
實作會利用 64 位 JIT 編譯程式在 Arm64 上發出多緩存器負載/存放區指令的能力。 此行為可讓程式以較少的指示處理較大的數據區塊。 跨各種網域的 .NET 應用程式應該會在支持這些功能的Arm64硬體上看到輸送量改善。 一些 基準將 運行時間削減了一半以上。
.NET SDK
單元測試
本節說明 .NET 9 中單元測試的更新:平行執行測試,以及終端機記錄器測試輸出。
平行執行測試
在 .NET 9 中, dotnet test
與 MSBuild 更完全整合。 因為 MSBuild 支援 平行建置,因此您可以平行跨不同目標架構執行相同項目的測試。 根據預設,MSBuild 會將平行進程數目限制為計算機上的處理器數目。 您也可以使用 -maxcpucount 參數來設定自己的限制。 如果您要離開平行處理原則,請將 TestTfmsInParallel
MSBuild 屬性設定為 false
。
終端機記錄器測試顯示
現在 MSBuild 終端機記錄器支援針對 的測試結果報告 dotnet test
。 當測試正在執行(顯示執行測試名稱)和測試完成之後,您都會得到更完整的測試報告(任何測試錯誤都會以較好的方式轉譯)。
如需終端機記錄器的詳細資訊,請參閱 dotnet build options。
.NET 工具向前復原
.NET 工具 是架構相依的應用程式,您可以全域或本機安裝,然後使用 .NET SDK 執行並安裝 .NET 運行時間。 這些工具,就像所有 .NET 應用程式一樣,以特定主要版本的 .NET 為目標。 根據預設,應用程式不會在較新版本的 .NET 上執行。 工具作者已能夠藉由設定 RollForward
MSBuild 屬性,選擇在較新版本的 .NET 運行時間上執行其工具。 不過,並非所有工具都這樣做。
的新選項dotnet tool install
可讓使用者決定應該如何執行 .NET 工具。 當您透過 dotnet tool install
安裝工具時,或透過 執行工具 dotnet tool run <toolname>
時,您可以指定名為 --allow-roll-forward
的新旗標。 這個選項會以向前復原模式 Major
設定工具。 如果相符的 .NET 版本無法使用,此模式可讓工具在較新的 .NET 主要版本上執行。 這項功能可協助早期採用者使用 .NET 工具,而不需要工具作者變更任何程序代碼。