.NET 6 史上最全攻略

MicrosoftReactor發表於2022-04-08

歡迎使用.NET 6。今天的版本是.NET 團隊和社群一年多努力的結果。C# 10 和F# 6 提供了語言改進,使您的程式碼更簡單、更好。效能大幅提升,我們已經看到微軟降低了託管雲服務的成本。.NET 6 是第一個原生支援Apple Silicon (Arm64) 的版本,並且還針對Windows Arm64 進行了改進。我們構建了一個新的動態配置檔案引導優化(PGO) 系統,該系統可提供僅在執行時才可能進行的深度優化。使用dotnet monitorOpenTelemetry改進了雲診斷。WebAssembly支援更有能力和效能。HTTP/3新增了新的API ,處理JSON數學和直接操作記憶體。.NET 6 將支援三年。開發人員已經開始將應用程式升級到.NET 6,我們在生產中聽到了很好的早期成果。.NET 6 已為您的應用程式做好準備。

您可以下載適用於Linux、macOS 和Windows 的.NET 6 。

請參閱ASP.NET CoreEntity FrameworkWindows Forms.NET MAUIYARPdotnet 監視器帖子,瞭解各種場景中的新增功能。

.NET 6 亮點

.NET 6 是:

該版本包括大約一萬次git 提交。即使這篇文章很長,它也跳過了許多改進。您必須下載並試用.NET 6 才能看到所有新功能。

支援

.NET 6 是一個長期支援(LTS) 版本,將支援三年。它支援多種作業系統,包括macOS Apple Silicon 和Windows Arm64。

Red Hat與.NET 團隊合作,在Red Hat Enterprise Linux 上支援.NET。在RHEL 8 及更高版本上,.NET 6 將可用於AMD 和Intel (x64\_64)、ARM (aarch64) 以及IBM Z 和LinuxONE (s390x) 架構。

請開始將您的應用程式遷移到.NET 6,尤其是.NET 5 應用程式。我們從早期採用者那裡聽說,從.NET Core 3.1 和.NET 5 升級到.NET 6 很簡單。

Visual Studio 2022Visual Studio 2022 for Mac支援.NET 6 。Visual Studio 2019、Visual Studio for Mac 8 或MSBuild 16 不支援它。如果要使用.NET 6,則需要升級到Visual Studio 2022(現在也是64 位)。Visual Studio Code C# 擴充套件支援.NET 6 。

Azure App 服務:

注意:如果您的應用已經在應用服務上執行.NET 6 預覽版或RC 版本,則在將.NET 6 執行時和SDK 部署到您所在區域後,它將在第一次重新啟動時自動更新。如果您部署了一個獨立的應用程式,您將需要重新構建和重新部署。

統一擴充套件平臺

.NET 6 為瀏覽器桌面物聯網移動應用程式提供了一個統一的平臺。底層平臺已更新,可滿足所有應用型別的需求,並便於在所有應用中重用程式碼。新功能和改進同時適用於所有應用程式,因此您在雲或移動裝置上執行的程式碼的行為方式相同並具有相同的優勢。

.NET 開發人員的範圍隨著每個版本的釋出而不斷擴大。機器學習WebAssembly是最近新增的兩個。例如,通過機器學習,您可以編寫在流資料中查詢異常的應用程式。使用WebAssembly,您可以在瀏覽器中託管.NET 應用程式,就像HTML 和JavaScript 一樣,或者將它們與HTML 和JavaScript 混合使用

最令人興奮的新增功能之一是.NET Multi-platform App UI (.NET MAUI)。您現在可以在單個專案中編寫程式碼,從而跨桌面和移動作業系統提供現代客戶端應用程式體驗。.NET MAUI 將比.NET 6 稍晚釋出。我們在.NET MAUI 上投入了大量時間和精力,很高興能夠釋出它並看到.NET MAUI 應用程式投入生產。

當然,.NET 應用程式也可以在家中使用Windows 桌面(使用Windows FormsWPF)以及使用ASP.NET Core 在雲中。它們是我們提供時間最長的應用程式型別,並且仍然非常受歡迎,我們在.NET 6 中對其進行了改進。

面向 .NET 6

繼續以廣泛平臺為主題,在所有這些作業系統上編寫.NET 程式碼很容易。

以 .NET 6 為目標,您需要使用.NET 6 目標框架,如下所示:

<TargetFramework>net6.0<TargetFramework>

net6.0 Target Framework Moniker (TFM) 使您可以訪問.NET 提供的所有跨平臺API。如果您正在編寫控制檯應用程式、ASP.NET Core 應用程式或可重用的跨平臺庫,這是最佳選擇。

如果您針對特定作業系統(例如編寫Windows 窗體或iOS 應用程式),那麼還有另一組TFM(每個都針對不言而喻的作業系統)供您使用。它們使您可以訪問所有net6.0的API以及一堆特定於作業系統的API。

  • net6.0-android
  • net6.0-ios
  • net6.0-maccatalyst
  • net6.0-tvos
  • net6.0-windows

每個無版本TFM 都相當於針對.NET 6 支援的最低作業系統版本。如果您想要具體或訪問更新的API,可以指定作業系統版本。

net6.0和net6.0-windows TFMs 都支援(與.NET 5 相同)。Android 和Apple TFM 是.NET 6 的新功能,目前處於預覽階段。稍後的.NET 6 更新將支援它們。

作業系統特定的 TFM 之間沒有相容性關係。 例如,net6.0-ios與 net6.0-tvos不相容。 如果您想共享程式碼,您需要使用帶有#if 語句的原始碼或帶有net6.0目的碼的二進位制檔案來實現。

效能

自從我們啟動.NET Core 專案以來,該團隊一直在不斷地關注效能。Stephen Toub在記錄每個版本的.NET 效能進展方面做得非常出色。歡迎檢視.NET 6 中的效能改進的帖子。在這篇文章中,裡面包括您想了解的重大效能改進,包括檔案IO、介面轉換、PGO 和System.Text.Json。

動態 PGO

動態輪廓引導優化(PGO)可以顯著提高穩態效能。例如,PGO 為TechEmpower JSON"MVC"套件的每秒請求數提高了26%(510K -\&gt; 640K)。

動態PGO 建立在分層編譯的基礎上,它使方法能夠首先非常快速地編譯(稱為"第0 層")以提高啟動效能,然後在啟用大量優化的情況下隨後重新編譯(稱為"第1 層")一旦該方法被證明是有影響的。該模型使方法能夠在第0 層中進行檢測,以允許對程式碼的執行進行各種觀察。在第1 層重新調整這些方法時,從第0 層執行收集的資訊用於更好地優化第1 層程式碼。這就是機制的本質。

動態PGO 的啟動時間將比預設執行時稍慢,因為在第0 層方法中執行了額外的程式碼來觀察方法行為。

要啟用動態 PGO,請在應用程式將執行的環境中設定 DOTNET\_TieredPGO=1。 您還必須確保啟用分層編譯(預設情況下)。 動態 PGO 是可選的,因為它是一種新的且有影響力的技術。 我們希望釋出選擇加入使用和相關反饋,以確保它經過全面壓力測試。 我們對分層編譯做了同樣的事情。 至少一個非常大的 Microsoft 服務支援並已在生產中使用動態 PGO。 我們鼓勵您嘗試一下。

您可以在.NET 6中的效能帖子中看到更多關於動態PGO 優勢的資訊,包括以下微基準,它測量特定LINQ 列舉器的成本。

private IEnumerator<long> _source = Enumerable.Range(0, long.MaxValue).GetEnumerator();

[Benchmark]
public void MoveNext() => _source.MoveNext();

這是有和沒有動態PGO 的結果。

方法意思是程式碼大小
PGO 已禁用1.905 納秒30乙
啟用PGO0.7071 納秒105乙

這是一個相當大的差異,但程式碼大小也有所增加,這可能會讓一些讀者感到驚訝。這是由JIT 生成的彙編程式碼的大小,而不是記憶體分配(這是一個更常見的焦點)。.NET 6 效能帖子對此有很好的解釋。

PGO 實現中常見的一種優化是"熱/冷分離",其中經常執行的方法部分(“熱”)在方法開始時靠近在一起,而不經常執行的方法部分(“冷”)是移到方法的末尾。這樣可以更好地使用指令快取,並最大限度地減少可能未使用的程式碼負載。

作為上下文,介面排程是 .NET 中最昂貴的呼叫型別。 非虛擬方法呼叫是最快的,甚至更快的是可以通過內聯消除的呼叫。 在這種情況下,動態 PGO 為 MoveNext 提供了兩個(替代)呼叫站點。 第一個 - 熱的 - 是對 Enumerable+RangeIterator.MoveNext的直接呼叫,另一個 - 冷的 - 是通過 IEnumerator<int>的虛擬介面呼叫。 如果大多數時候最熱門的人都被叫到,那將是一個巨大的勝利。

這就是魔法。當 JIT 檢測此方法的第 0 層程式碼時,包括檢測此介面排程以跟蹤每次呼叫時 \_source的具體型別。 JIT 發現每次呼叫都在一個名為 Enumerable+RangeIterator的型別上,這是一個私有類,用於在 Enumerable實現內部實現 Enumerable.Range。因此,對於第 1 層,JIT 已發出檢查以檢視 \_source的型別是否為 Enumerable+RangeIterator:如果不是,則跳轉到我們之前強調的執行正常介面排程的冷部分。但如果是 - 基於分析資料,預計絕大多數時間都是這種情況 - 然後它可以繼續直接呼叫非虛擬化的 Enumerable+RangeIterator.MoveNext方法。不僅如此,它還認為內聯 MoveNext 方法是有利可圖的。最終效果是生成的彙編程式碼有點大,但針對預期最常見的確切場景進行了優化。當我們開始構建動態 PGO 時,這些就是我們想要的那種勝利。

動態PGO 將在RyuJIT 部分再次討論。

檔案 IO 改進

FileStream幾乎完全用.NET 6 重寫,重點是提高非同步檔案IO 效能。在Windows 上,實現不再使用阻塞API,並且可以 快幾倍 !我們還改進了所有平臺上的記憶體使用。在第一次非同步操作(通常分配)之後,我們已經使非同步操作 免分配 !此外,我們已經使Windows 和Unix 實現不同的邊緣情況的行為統一(這是可能的)。

這種重寫的效能改進使所有作業系統受益。對Windows 的好處是最大的,因為它遠遠落後。macOS 和Linux 使用者也應該會看到顯著FileStream的效能改進。

以下基準將100 MB 寫入新檔案。

private byte[] _bytes = new byte[8_000];

[Benchmark]
public async Task Write100MBAsync()
{
    using FileStream fs = new("file.txt", FileMode.Create, FileAccess.Write, FileShare.None, 1, FileOptions.Asynchronous);
    for (int i = 0; i < 100_000_000 / 8_000; i++)
        await fs.WriteAsync(_bytes);
}

在帶有SSD 驅動器的Windows 上,我們觀察到 4倍的加速 和超過 1200倍的分配下降

方法執行意思是比率已分配
寫100MBAsync.NET 5.01,308.2 毫秒1.003,809 KB
寫100MBAsync.NET 6.0306.8 毫秒0.243 KB

我們還認識到需要更高效能的檔案 IO 功能:併發讀取和寫入,以及分散/收集 IO。 針對這些情況,我們為 System.IO.FileSystem.IO.RandomAccess類引入了新的 API。

async Task AllOrNothingAsync(string path, IReadOnlyList<ReadOnlyMemory<byte>> buffers)
{
    using SafeFileHandle handle = File.OpenHandle(
        path, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.Asynchronous,
        preallocationSize: buffers.Sum(buffer => buffer.Length)); // hint for the OS to pre-allocate disk space

    await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0); // on Linux it's translated to a single sys-call!

該示例演示:

預分配大小功能提高了效能,因為寫入操作不需要擴充套件檔案,並且檔案不太可能被碎片化。這種方法提高了可靠性,因為寫入操作將不再因空間不足而失敗,因為空間已被保留。Scatter/Gather IO API 減少了寫入資料所需的系統呼叫次數。

更快的介面檢查和轉換

介面鑄造效能提高了16% - 38%。這種改進對於C# 與介面之間的模式匹配特別有用。

這張圖表展示了一個有代表性的基準測試的改進規模。

將.NET 執行時的一部分從C++ 遷移到託管C# 的最大優勢之一是它降低了貢獻的障礙。這包括介面轉換,它作為早期的.NET 6 更改移至C#。.NET 生態系統中懂C# 的人比懂C++ 的人多(而且執行時使用具有挑戰性的C++ 模式)。僅僅能夠閱讀構成執行時的一些程式碼是培養以各種形式做出貢獻的信心的重要一步。

歸功於 Ben Adams

System.Text.Json 源生成器

我們為System.Text.Json 新增了一個原始碼生成器,它避免了在執行時進行反射和程式碼生成的需要,並且可以在構建時生成最佳序列化程式碼。序列化程式通常使用非常保守的技術編寫,因為它們必須如此。但是,如果您閱讀自己的序列化原始碼(使用序列化程式),您可以看到明顯的選擇應該是什麼,可以使序列化程式在您的特定情況下更加優化。這正是這個新的源生成器所做的。

除了提高效能和減少記憶體之外,原始碼生成器還生成最適合裝配修整的程式碼。這有助於製作更小的應用程式。

序列化POCO是一種非常常見的場景。使用新的原始碼生成器,我們觀察到序列化速度比我們的基準 快1.6倍

方法意思是標準差比率
序列器243.1 納秒9.54 納秒1.00
SrcGenSerializer149.3 納秒1.91 納秒0.62

TechEmpower快取基準測試平臺或框架對來自資料庫的資訊進行記憶體快取。基準測試的.NET 實現執行快取資料的JSON 序列化,以便將其作為響應傳送到測試工具。

請求 / 要求
net5.0243,0003,669,151
網6.0260,9283,939,804
net6.0 + JSON 原始碼生成364,2245,499,468

我們觀察到約100K RPS 增益( 增加約40%)。與 MemoryCache 效能改進相結合時,.NET 6 的吞吐量比.NET 5 高50% !

 C# 10

歡迎來到C# 10。C# 10 的一個主要主題是繼續從C# 9 中的頂級語句開始的簡化之旅。新功能從 Program.cs中刪除了更多的儀式,導致程式只有一行。 他們的靈感來自於與沒有 C# 經驗的人(學生、專業開發人員和其他人)交談,並瞭解什麼對他們來說最有效且最直觀。

大多數.NET SDK 模板都已更新,以提供現在可以使用C# 10 實現的更簡單、更簡潔的體驗。我們收到反饋說,有些人不喜歡新模板,因為它們不適合專家,刪除物件導向,刪除在編寫C# 的第一天學習的重要概念,或鼓勵在一個檔案中編寫整個程式。客觀地說,這些觀點都不正確。新模型同樣適用於作為專業開發人員的學生。但是,它與.NET 6 之前的C 派生模型不同。

C# 10 中還有其他一些功能和改進,包括記錄結構。

全域性使用指令

全域性using 指令讓您using只需指定一次指令並將其應用於您編譯的每個檔案。

以下示例顯示了語法的廣度:

  • global using System;
  • global using static System.Console;
  • global using Env = System.Environment;

您可以將global using語句放在任何 .cs 檔案中,包括在 Program.cs中。

隱式 usings 是一個MSBuild 概念,它會根據SDK自動新增一組指令。例如,控制檯應用程式隱式使用不同於ASP.NET Core。

隱式使用是可選的,並在a 中啟用PropertyGroup

  • <ImplicitUsings\&gt;enable\&lt;/ImplicitUsings>

隱式使用對於現有專案是可選的,但預設包含在新C# 專案中。有關詳細資訊,請參閱隱式使用

檔案範圍的名稱空間

檔案範圍的名稱空間使您能夠宣告整個檔案的名稱空間,而無需將剩餘內容巢狀在{ ...}中. 只允許一個,並且必須在宣告任何型別之前出現。

新語法是單個的一行:

namespaceMyNamespace;

classMyClass{...}// Not indented

這種新語法是三行縮排樣式的替代方案:

namespaceMyNamespace
{
classMyClass{...}// Everything is indented
}

好處是在整個檔案位於同一個名稱空間中的極其常見的情況下減少縮排。

記錄結構

C# 9 將記錄作為一種特殊的面向值的類形式引入。在C# 10 中,您還可以宣告結構記錄。C# 中的結構已經具有值相等,但記錄結構新增了==運算子和IEquatable<T>的實現,以及基於值的ToString實現:

public record structPerson
{
publicstringFirstName{get; init;}
publicstringLastName{get; init;}
}

就像記錄類一樣,記錄結構可以是"位置的",這意味著它們有一個主建構函式,它隱式宣告與引數對應的公共成員:

public record structPerson(stringFirstName,stringLastName);

但是,與記錄類不同,隱式公共成員是_可變的自動實現的屬性_。這樣一來,記錄結構就成為了元組的自然成長故事。例如,如果您有一個返回型別(string FirstName, string LastName),並且您希望將其擴充套件為命名型別,您可以輕鬆地宣告相應的位置結構記錄並維護可變語義。

如果你想要一個具有隻讀屬性的不可變記錄,你可以宣告整個記錄結構readonly(就像你可以其他結構一樣):

publicreadonly record structPerson(stringFirstName,stringLastName);

C# 10 不僅支援記錄結構,還支援_所有_結構以及匿名型別的with表示式:

var updatedPerson = person with{FirstName=&quot;Mary&quot;};

F# 6

F# 6旨在讓F# 更簡單、更高效。這適用於語言設計、庫和工具。我們對F# 6(及更高版本)的目標是消除語言中讓使用者感到驚訝或阻礙學習F# 的極端情況。我們很高興能與F# 社群合作進行這項持續的努力。

讓 F# 更快、更互操作

新語法task {…}直接建立一個任務並啟動它。這是 F# 6 中最重要的功能之一,它使非同步任務更簡單、效能更高,並且與 C# 和其他 .NET 語言的互操作性更強。以前,建立 .NET 任務需要使用async {…}來建立任務並呼叫Async.StartImmediateAsTask

該功能task {…}建立在稱為“可恢復程式碼”RFC FS-1087的基礎之上。可恢復程式碼是一個核心特性,我們希望在未來使用它來構建其他高效能非同步和屈服狀態機。

F# 6 還為庫作者新增了其他效能特性,包括InlineIfLambda 和F#活動模式的未裝箱表示。一個特別顯著的效能改進在於列表和陣列表示式的編譯,現在它們的速度提高了 4倍 ,並且除錯也更好、更簡單。

讓 F# 更易學、更統一

F# 6 啟用expr[idx]索引語法。到目前為止,F# 一直使用 expr.[idx] 進行索引。刪除點符號是基於第一次使用 F# 使用者的反覆反饋,點的使用與他們期望的標準實踐有不必要的差異。在新程式碼中,我們建議系統地使用新的expr[idx]索引語法。作為一個社群,我們都應該切換到這種語法。

F# 社群為使 F# 語言在 F# 6 中更加統一做出了重要改進。其中最重要的是消除了 F# 縮排規則中的一些不一致和限制。使 F# 更加統一的其他設計新增包括新增as圖案;在計算表示式中允許“過載自定義操作”(對 DSL 有用);允許_丟棄use繫結並允許%B在輸出中進行二進位制格式化。F# 核心庫新增了用於複製和更新列表、陣列和序列的新函式,以及其他NativePtr內在函式。自 2.0 起棄用的 F# 的一些舊功能現在會導致錯誤。其中許多更改更好地使 F# 與您的期望保持一致,從而減少意外。

F# 6 還增加了對 F# 中其他“隱式”和“型別導向”轉換的支援。這意味著更少的顯式向上轉換,併為 .NET 樣式的隱式轉換新增了一流的支援。F# 也進行了調整,以更好地適應使用 64 位整數的數字庫時代,並隱式擴充套件了 32 位整數。

改進 F# 工具

F# 6 中的工具改進使日常編碼更容易。新的"管道除錯"允許您單步執行、設定斷點並檢查 F# 管道語法input |> f1 |> f2 的中間值。陰影值的除錯顯示已得到改進,消除了除錯時常見的混淆源。F# 工具現在也更高效,F# 編譯器並行執行解析階段。F# IDE 工具也得到了改進。F# 指令碼現在更加健壯,允許您通過global.json檔案固定使用的 .NET SDK 版本。

熱過載

Hot Reload 是另一個效能特性,專注於開發人員的生產力。它使您能夠對正在執行的應用程式進行各種程式碼編輯,從而縮短您等待應用程式重新構建、重新啟動或重新導航到您在進行程式碼更改後所在位置所需的時間。

Hot Reload 可通過dotnet watch CLI 工具和 Visual Studio 2022 使用。您可以將 Hot Reload 與多種應用型別一起使用,例如 ASP.NET Core、Blazor、.NET MAUI、控制檯、Windows 窗體 (WinForms)、WPF、WinUI 3、Azure 函式等。

使用 CLI 時,只需使用 啟動您的 .NET 6 應用程式dotnet watch,進行任何受支援的編輯,然後在儲存檔案時(如在 Visual Studio Code 中),這些更改將立即應用。如果不支援更改,詳細資訊將記錄到命令視窗。

此影像顯示了一個使用dotnet watch. 我對.cs檔案和.cshtml檔案進行了編輯(如日誌中所述),兩者都應用於程式碼並在不到半秒的時間內非常快速地反映在瀏覽器中。

使用 Visual Studio 2022 時,只需啟動您的應用程式,進行支援的更改,然後使用新的"熱過載"按鈕(如下圖所示)應用這些更改。您還可以通過同一按鈕上的下拉選單選擇在儲存時應用更改。使用 Visual Studio 2022 時,熱過載可用於多個 .NET 版本,適用於 .NET 5+、.NET Core 和 .NET Framework。例如,您將能夠對按鈕的OnClickEvent處理程式進行程式碼隱藏更改。應用程式的Main方法不支援它。

注意:RuntimeInformation.FrameworkDescription中存在一個錯誤,該錯誤將在該影像中展示,很快就會修復。

Hot Reload 還與現有的 Edit and Continue 功能(在斷點處停止時)以及用於實時編輯應用程式 UI 的 XAML Hot Reload 協同工作。目前支援 C# 和 Visual Basic 應用程式(不是 F#)。

安全

.NET 6 中的安全性得到了顯著改進。它始終是團隊關注的重點,包括威脅建模、加密和深度防禦防禦。

在 Linux 上,我們依賴OpenSSL進行所有加密操作,包括 TLS(HTTPS 必需)。在 macOS 和 Windows 上,我們依賴作業系統提供的功能來實現相同的目的。對於每個新版本的 .NET,我們經常需要新增對新版本 OpenSSL 的支援。.NET 6 增加了對OpenSSL 3的支援。

OpenSSL 3 的最大變化是改進的FIPS 140-2模組和更簡單的許可。

.NET 6 需要 OpenSSL 1.1 或更高版本,並且會更喜歡它可以找到的最高安裝版本的 OpenSSL,直到幷包括 v3。在一般情況下,當您使用的 Linux 發行版預設切換到 OpenSSL 3 時,您最有可能開始使用 OpenSSL 3。大多數發行版還沒有這樣做。例如,如果您在 Red Hat 8 或 Ubuntu 20.04 上安裝 .NET 6,您將不會(在撰寫本文時)開始使用 OpenSSL 3。

OpenSSL 3、Windows 10 21H1 和 Windows Server 2022 都支援ChaCha20Poly1305。您可以.NET 6 中使用這種新的經過身份驗證的加密方案(假設您的環境支援它)。

感謝 Kevin Jones對 ChaCha20Poly1305 的 Linux 支援。

我們還發布了新的執行時安全緩解路線圖。重要的是,您使用的執行時不受教科書攻擊型別的影響。我們正在滿足這一需求。在 .NET 6 中,我們構建了W^X英特爾控制流強制技術(CET)的初始實現。W^X 完全受支援,預設為 macOS Arm64 啟用,並且可以選擇加入其他環境。CET 是所有環境的選擇加入和預覽。我們希望在 .NET 7 中的所有環境中預設啟用這兩種技術。

Arm64

這些天來,對於膝上型電腦、雲硬體和其他裝置來說,Arm64 令人興奮不已。我們對 .NET 團隊感到同樣興奮,並正在盡最大努力跟上這一行業趨勢。我們直接與 Arm Holdings、Apple 和 Microsoft 的工程師合作,以確保我們的實施是正確和優化的,並且我們的計劃保持一致。這些密切的合作伙伴關係對我們幫助很大。

  • 特別感謝 Apple 在 M1 晶片釋出之前向我們的團隊傳送了一蒲式耳 Arm64 開發套件供我們使用,並提供了重要的技術支援。
  • 特別感謝 Arm Holdings,他們的工程師對我們的 Arm64 更改進行了程式碼審查,並進行了效能改進。

在此之前,我們通過 .NET Core 3.0 和 Arm32 新增了對 Arm64 的初始支援。該團隊在最近的幾個版本中都對 Arm64 進行了重大投資,並且在可預見的未來這將繼續下去。在 .NET 6 中,我們主要關注在 macOS 和 Windows Arm64 作業系統上支援新的 Apple Silicon 晶片和x64 模擬場景

您可以在 macOS 11+ 和 Windows 11+ Arm64 作業系統上安裝 Arm64 和 x64 版本的 .NET。我們必須做出多種設計選擇和產品更改以確保其奏效。

我們的策略是“親原生架構”。我們建議您始終使用與原生架構相匹配的 SDK,即 macOS 和 Windows Arm64 上的 Arm64 SDK。SDK 是大量的軟體。在 Arm64 晶片上本地執行的效能將比模擬高得多。我們更新了 CLI 以簡化操作。我們永遠不會專注於優化模擬 x64。

預設情況下,如果您dotnet run是帶有 Arm64 SDK 的 .NET 6 應用程式,它將作為 Arm64 執行。您可以使用引數輕鬆切換到以 x64 執行,例如-adotnet run -a x64. 相同的論點適用於其他 CLI 動詞。有關更多資訊,請參閱 適用於macOS 和Windows Arm64 的.NET 6 RC2 更新

我想確保涵蓋其中的一個微妙之處。當您使用-a x64時,SDK 仍以 Arm64 方式原生執行。.NET SDK 體系結構中存在程式邊界的固定點。在大多數情況下,一個程式必須全是 Arm64 或全是 x64。我正在簡化一點,但 .NET CLI 會等待 SDK 架構中的最後一個程式建立,然後將其作為您請求的晶片架構(如 x64)啟動。這就是您的程式碼執行的過程。這樣,作為開發人員,您可以獲得 Arm64 的好處,但您的程式碼可以在它需要的過程中執行。這僅在您需要將某些程式碼作為 x64 執行時才相關。如果你不這樣做,那麼你可以一直以 Arm64 的方式執行所有東西,這很棒。

Arm64支援

對於 macOS 和 Windows Arm64,以下是您需要了解的要點:

  • 支援並推薦 .NET 6 Arm64 和 x64 SDK。
  • 支援所有支援的 Arm64 和 x64 執行時。
  • .NET Core 3.1 和 .NET 5 SDK 可以工作,但提供的功能較少,並且在某些情況下不受完全支援。
  • dotnet test尚未與 x64 模擬一起正常工作。我們正在努力dotnet test將作為6.0.200 版本的一部分進行改進,並且可能更早。

有關更多完整資訊,請參閱.NET 對macOS 和Windows Arm64的支援。

此討論中缺少Linux。它不像macOS 和Windows 那樣支援x64 模擬。因此,這些新的CLI 特性和支援方法並不直接適用於Linux,Linux 也不需要它們。

視窗Arm64

我們有一個簡單的工具來演示.NET 執行的環境。

C:Usersrich>dotnet tool install -g dotnet-runtimeinfo
You can invoke the tool using the following command: dotnet-runtimeinfo
Tool 'dotnet-runtimeinfo' (version '1.0.5') was successfully installed.

C:Usersrich>dotnet runtimeinfo
         42
         42              ,d                             ,d
         42              42                             42
 ,adPPYb,42  ,adPPYba, MM42MMM 8b,dPPYba,   ,adPPYba, MM42MMM
a8"    `Y42 a8"     "8a  42    42P'   `"8a a8P_____42   42
8b       42 8b       d8  42    42       42 8PP"""""""   42
"8a,   ,d42 "8a,   ,a8"  42,   42       42 "8b,   ,aa   42,
 `"8bbdP"Y8  `"YbbdP"'   "Y428 42       42  `"Ybbd8"'   "Y428

**.NET information
Version: 6.0.0
FrameworkDescription: .NET 6.0.0-rtm.21522.10
Libraries version: 6.0.0-rtm.21522.10
Libraries hash: 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6

**Environment information
ProcessorCount: 8
OSArchitecture: Arm64
OSDescription: Microsoft Windows 10.0.22494
OSVersion: Microsoft Windows NT 10.0.22494.0

如您所見,該工具在Windows Arm64 上本機執行。我將向您展示ASP.NET Core 的樣子。

macOS Arm64

您可以看到在macOS Arm64 上的體驗是相似的,並且還展示了架構目標。

rich@MacBook-Air app % dotnet --version
6.0.100
rich@MacBook-Air app % dotnet --info | grep RID
 RID:         osx-arm64
rich@MacBook-Air app % cat Program.cs 
using System.Runtime.InteropServices;
using static System.Console;

WriteLine($"Hello, {RuntimeInformation.OSArchitecture} from {RuntimeInformation.FrameworkDescription}!");
rich@MacBook-Air app % dotnet run
Hello, Arm64 from .NET 6.0.0-rtm.21522.10!
rich@MacBook-Air app % dotnet run -a x64
Hello, X64 from .NET 6.0.0-rtm.21522.10!
rich@MacBook-Air app % 

這張圖片展示了Arm64 執行是Arm64 SDK 的預設設定,以及使用-a引數在目標Arm64 和x64 之間切換是多麼容易。完全相同的體驗適用於Windows Arm64。

此影像演示了相同的內容,但使用的是ASP.NET Core。我正在使用與您在上圖中看到的相同的.NET 6 Arm64 SDK。

Arm64 上的 Docker

Docker 支援在本機架構和模擬中執行的容器,本機架構是預設的。這看起來很明顯,但當大多數Docker Hub 目錄都是面向x64 時,這可能會讓人感到困惑。您可以使用-platform linux/amd64來請求x64 影像。

我們僅支援在Arm64 作業系統上執行Linux Arm64 .NET 容器映像。這是因為我們從不支援在QEMU中執行.NET ,這是Docker 用於架構模擬的。看來這可能是由於 QEMU 的限制

此影像演示了我們維護的控制檯示例:mcr.microsoft.com/dotnet/samples。 這是一個有趣的示例,因為它包含一些基本邏輯,用於列印您可以使用的CPU 和記憶體限制資訊。我展示的影像設定了CPU 和記憶體限制。

自己試試吧:docker run --rm mcr.microsoft.com/dotnet/samples

Arm64 效能

Apple Silicon 和x64 模擬支援專案非常重要,但是,我們也普遍提高了Arm64 效能。

此影像演示了將堆疊幀的內容清零的改進,這是一種常見的操作。綠線是新行為,而橙色線是另一個(不太有益的)實驗,兩者都相對於基線有所改善,由藍線表示。對於此測試,越低越好。

容器

.NET 6 更適合容器,主要基於本文中討論的所有改進,適用於Arm64 和x64。我們還進行了有助於各種場景的關鍵更改。使用.NET 6 驗證容器改進演示了其中一些改進正在一起測試。

Windows 容器改進和新環境變數也包含在11 月9 日(明天)釋出的11 月.NET Framework 4.8 容器更新中。

釋出說明可在我們的docker 儲存庫中找到:

Windows 容器

.NET 6 增加了對Windows 程式隔離容器的支援。如果您在 Azure Kubernetes 服務(AKS) 中使用Windows 容器,那麼您依賴於程式隔離的容器。程式隔離容器可以被認為與Linux 容器非常相似。Linux 容器使用cgroups,Windows 程式隔離容器使用Job Objects。Windows 還提供Hyper-V 容器,通過更強大的虛擬化提供更大的隔離。Hyper-V 容器的.NET 6 沒有任何變化。

此更改的主要價值是現在Environment.ProcessorCount將使用Windows 程式隔離容器報告正確的值。如果在64 核機器上建立2 核容器,Environment.ProcessorCount將返回2. 在以前的版本中,此屬性將報告機器上的處理器總數,與Docker CLI、Kubernetes 或其他容器編排器/執行時指定的限制無關。此值被.NET 的各個部分用於擴充套件目的,包括.NET 垃圾收集器(儘管它依賴於相關的較低階別的API)。社群庫也依賴此API 進行擴充套件。

我們最近在AKS 上使用大量pod 在生產中的Windows 容器上與客戶驗證了這一新功能。他們能夠以50% 的記憶體(與他們的典型配置相比)成功執行,這是以前導致異常的OutOfMemoryException水平StackOverflowException。他們沒有花時間找到最低記憶體配置,但我們猜測它明顯低於他們典型記憶體配置的50%。由於這一變化,他們將轉向更便宜的Azure 配置,從而節省資金。只需升級即可,這是一個不錯的、輕鬆的勝利。

優化縮放

我們從使用者那裡聽說,某些應用程式在Environment.ProcessorCount報告正確的值時無法實現最佳擴充套件。如果這聽起來與您剛剛閱讀的有關Windows 容器的內容相反,那麼它有點像。.NET 6 現在提供DOTNET\_PROCESSOR\_COUNT 環境變數來手動控制Environment.ProcessorCount的值。在典型的用例中,應用程式可能在64 核機器上配置為4核,並且在8或16核方面擴充套件得最好。此環境變數可用於啟用該縮放。

這個模型可能看起來很奇怪,其中Environment.ProcessorCount--cpus(通過Docker CLI)值可能不同。預設情況下,容器執行時面向核心等價物,而不是實際核心。這意味著,當你說你想要4 個核心時,你得到的CPU 時間與4 個核心相當,但你的應用程式可能(理論上)在更多的核心上執行,甚至在短時間內在64 核機器上執行所有64 個核心。這可能使您的應用程式能夠在超過4 個執行緒上更好地擴充套件(繼續示例),並且分配更多可能是有益的。這假定執行緒分配基於 Environment.ProcessorCount的值。如果您選擇設定更高的值,您的應用程式可能會使用更多記憶體。對於某些工作負載,這是一個簡單的權衡。至少,這是一個您可以測試的新選項。

Linux 和Windows 容器均支援此新功能。

Docker 還提供了一個CPU 組功能,您的應用程式可以關聯到特定的核心。在這種情況下不建議使用此功能,因為應用程式可以訪問的核心數量是具體定義的。我們還看到了將它與Hyper-V 容器一起使用時的一些問題,並且它並不是真正適用於那種隔離模式。

Debian 11 "bullseye"

我們密切關注Linux 發行版的生命週期和釋出計劃,並嘗試代表您做出最佳選擇。Debian 是我們用於預設Linux 映像的Linux 發行版。如果您6.0從我們的一個容器儲存庫中提取標籤,您將提取一個Debian 映像(假設您使用的是Linux 容器)。對於每個新的.NET 版本,我們都會考慮是否應該採用新的Debian 版本。

作為一項政策,我們不會為了方便標籤而更改Debian 版本,例如6.0, mid-release。如果我們這樣做了,某些應用程式肯定會崩潰。這意味著,在釋出開始時選擇Debian 版本非常重要。此外,這些影像得到了很多使用,主要是因為它們是"好標籤"的引用。

Debian 和.NET 版本自然不會一起計劃。當我們開始.NET 6 時,我們看到Debian "bullseye" 可能會在2021 年釋出。我們決定從釋出開始就押注於Bullseye我們開始使用.NET 6 Preview 1釋出基於靶心的容器映像,並決定不再回頭。賭注是.NET 6 版本會輸掉與靶心版本的競爭。到8 月8 日,我們仍然不知道Bullseye 什麼時候發貨,距離我們自己的版本釋出還有三個月,即11 月8 日。我們不想在預覽版Linux 上釋出生產.NET 6,但我們堅持我們會輸掉這場競賽的計劃很晚。

當Debian 11 "bullseye"於8 月14 日釋出時,我們感到非常驚喜。我們輸掉了比賽,但贏得了賭注。這意味著預設情況下,.NET 6 使用者從第一天開始就可以獲得最佳和最新的Debian。我們相信Debian 11 和.NET 6 將是許多使用者的絕佳組合。抱歉,剋星,我們中了靶心

較新的發行版在其軟體包提要中包含各種軟體包的較新主要版本,並且通常可以更快地獲得CVE 修復。這是對較新核心的補充。新發行版可以更好地為使用者服務。

再往前看,我們很快就會開始計劃對Ubuntu 22.04的支援。Ubuntu是另一個Debian 系列發行版,深受.NET 開發人員的歡迎。我們希望為新的Ubuntu LTS 版本提供當日支援。

Tianon Gravi 致敬,感謝他們為社群維護Debian 映像並在我們有問題時幫助我們。

Dotnet Monitor

dotnet monitor是容器的重要診斷工具。它作為 sidecar 容器映象已經有一段時間了,但處於不受支援的"實驗"狀態。作為.NET 6 的一部分,我們正在釋出一個基於.NET 6 的dotnet monitor映像,該映像在生產中得到完全支援。

dotnet monitor已被Azure App Service 用作其ASP.NET Core Linux 診斷體驗的實現細節。這是預期的場景之一,建立在dotnet monitor 之上,以提供更高階別和更高價值的體驗。

您現在可以拉取新影像:

docker pull mcr.microsoft.com/dotnet/monitor:6.0

dotnet monitor使從.NET 程式訪問診斷資訊(日誌、跟蹤、程式轉儲)變得更加容易。在桌上型電腦上訪問所需的所有診斷資訊很容易,但是,這些熟悉的技術在使用容器的生產環境中可能不起作用。dotnet monitor提供了一種統一的方式來收集這些診斷工件,無論是在您的桌面計算機上還是在Kubernetes 叢集中執行。收集這些診斷工件有兩種不同的機制:

  • 用於臨時收集工件的 HTTP API。當您已經知道您的應用程式遇到問題並且您有興趣收集更多資訊時,您可以呼叫這些API 端點。
  • 基於規則的配置 觸發器,用於始終線上收集工件。您可以配置規則以在滿足所需條件時收集診斷資料,例如,當您持續高CPU 時收集程式轉儲。

dotnet monitor為.NET 應用程式提供了一個通用的診斷API,可以使用任何工具在任何地方工作。“通用API”不是.NET API,而是您可以呼叫和查詢的Web API。dotnet monitor包括一個ASP.NET Web 伺服器,它直接與.NET 執行時中的診斷伺服器互動並公開來自診斷伺服器的資料設計dotnet monitor可實現生產中的高效能監控和安全使用,以控制對特權資訊的訪問。dotnet monitor通過非Internet 可定址的unix domain socket與執行時互動——跨越容器邊界。該模型通訊模型非常適合此用例。

結構化 JSON 日誌

JSON 格式化程式現在是aspnet.NET 6 容器映像中的預設控制檯記錄器。.NET 5 中的預設設定為簡單的控制檯格式化程式。進行此更改是為了使預設配置與依賴機器可讀格式(如JSON)的自動化工具一起使用。

影像的輸出現在如下所示aspnet

$ docker run --rm -it -p 8000:80 mcr.microsoft.com/dotnet/samples:aspnetapp
{"EventId":60,"LogLevel":"Warning","Category":"Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository","Message":"Storing keys in a directory u0027/root/.aspnet/DataProtection-Keysu0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.","State":{"Message":"Storing keys in a directory u0027/root/.aspnet/DataProtection-Keysu0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.","path":"/root/.aspnet/DataProtection-Keys","{OriginalFormat}":"Storing keys in a directory u0027{path}u0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed."}}
{"EventId":35,"LogLevel":"Warning","Category":"Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager","Message":"No XML encryptor configured. Key {86cafacf-ab57-434a-b09c-66a929ae4fd7} may be persisted to storage in unencrypted form.","State":{"Message":"No XML encryptor configured. Key {86cafacf-ab57-434a-b09c-66a929ae4fd7} may be persisted to storage in unencrypted form.","KeyId":"86cafacf-ab57-434a-b09c-66a929ae4fd7","{OriginalFormat}":"No XML encryptor configured. Key {KeyId:B} may be persisted to storage in unencrypted form."}}
{"EventId":14,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Now listening on: http://[::]:80","State":{"Message":"Now listening on: http://[::]:80","address":"http://[::]:80","{OriginalFormat}":"Now listening on: {address}"}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Application started. Press Ctrlu002BC to shut down.","State":{"Message":"Application started. Press Ctrlu002BC to shut down.","{OriginalFormat}":"Application started. Press Ctrlu002BC to shut down."}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Hosting environment: Production","State":{"Message":"Hosting environment: Production","envName":"Production","{OriginalFormat}":"Hosting environment: {envName}"}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Content root path: /app","State":{"Message":"Content root path: /app","contentRoot":"/app","{OriginalFormat}":"Content root path: {contentRoot}"}}

Logging\_\_Console\_\_FormatterName可以通過設定或取消設定環境變數或通過程式碼更改來更改記錄器格式型別(有關更多詳細資訊,請參閱控制檯日誌格式)。

更改後,您將看到如下輸出(就像.NET 5 一樣):

$ docker run --rm -it -p 8000:80 -e Logging__Console__FormatterName="" mcr.microsoft.com/dotnet/samples:aspnetapp
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {8d4ddd1d-ccfc-4898-9fe1-3e7403bf23a0} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app

注意:此更改不會影響開發人員計算機上的.NET SDK,例如dotnet run.此更改特定於aspnet容器映像。

支援 OpenTelemetry 指標

作為我們關注可觀察性的一部分,我們一直在為最後幾個.NET 版本新增對 OpenTelemetry 的支援。在.NET 6 中,我們新增了對OpenTelemetry Metrics API的支援。通過新增對OpenTelemetry 的支援,您的應用程式可以與其他OpenTelemetry系統無縫互操作。

System.Diagnostics.MetricsOpenTelemetry Metrics API 規範的.NET 實現。Metrics API 是專門為處理原始測量而設計的,目的是高效、同時地生成這些測量的連續摘要。

API 包括Meter可用於建立儀器物件的類。API 公開了四個工具類:CounterHistogramObservableCounter和,ObservableGauge以支援不同的度量方案。此外,API 公開MeterListener該類以允許收聽儀器記錄的測量值,以用於聚合和分組目的。

OpenTelemetry .NET 實現將被擴充套件以使用這些新的API,這些API 新增了對Metrics 可觀察性場景的支援。

圖書館測量記錄示例

     Meter meter = new Meter("io.opentelemetry.contrib.mongodb", "v1.0");
    Counter<int> counter = meter.CreateCounter<int>("Requests");
    counter.Add(1);
    counter.Add(1, KeyValuePair.Create<string, object>("request", "read"));

聽力示例

  MeterListener listener = new MeterListener();
    listener.InstrumentPublished = (instrument, meterListener) =>
    {
        if (instrument.Name == "Requests" && instrument.Meter.Name == "io.opentelemetry.contrib.mongodb")
        {
            meterListener.EnableMeasurementEvents(instrument, null);
        }
    };
    listener.SetMeasurementEventCallback<int>((instrument, measurement, tags, state) =>
    {
        Console.WriteLine($"Instrument: {instrument.Name} has recorded the measurement {measurement}");
    });
    listener.Start();

Windows Forms

我們繼續在 Windows 窗體中進行重要改進。.NET 6 包括更好的控制元件可訪問性、設定應用程式範圍的預設字型、模板更新等的能力。

可訪問性改進

在此版本中,我們新增了用於CheckedListBoxLinkLabelPanelScrollBarTabControlTrackBarUIA 提供程式,它們使講述人等工具和測試自動化能夠與應用程式的元素進行互動。

預設字型

您現在可以使用.Application.SetDefaultFont

voidApplication.SetDefaultFont(Font font)

最小的應用程式

以下是帶有 .NET 6 的最小Windows 窗體應用程式

class Program
{
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new Form1());
    }
}

作為.NET 6 版本的一部分,我們一直在更新大多數模板,使其更加現代和簡約,包括Windows 窗體。我們決定讓Windows 窗體模板更傳統一些,部分原因是需要將[STAThread]屬性應用於應用程式入口點。然而,還有更多的戲劇而不是立即出現在眼前。

ApplicationConfiguration.Initialize()是一個源生成API,它在後臺發出以下呼叫:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetDefaultFont(newFont(...));
Application.SetHighDpiMode(HighDpiMode.SystemAware);

這些呼叫的引數可通過csproj 或props 檔案中的MSBuild 屬性進行配置。

Visual Studio 2022 中的Windows 窗體設計器也知道這些屬性(目前它只讀取預設字型),並且可以向您顯示您的應用程式,就像它在執行時一樣:

模板更新

C# 的Windows 窗體模板已更新,以支援新的應用程式引導、global using指令、檔案範圍的名稱空間和可為空的引用型別。

更多執行時 designers

現在您可以構建通用設計器(例如,報表設計器),因為.NET 6 具有設計器和與設計器相關的基礎架構所缺少的所有部分。有關詳細資訊,請參閱此部落格文章

單檔案應用

在.NET 6中,已為 Windows 和macOS 啟用記憶體中單檔案應用程式。在.NET 5 中,這種部署型別僅限於 Linux。您現在可以為所有受支援的作業系統釋出作為單個檔案部署和啟動的單檔案二進位制檔案。單檔案應用不再將任何核心執行時程式集提取到臨時目錄。

這種擴充套件功能基於稱為"超級主機"的構建塊。"apphost" 是在非單檔案情況下啟動應用程式的可執行檔案,例如myapp.exe./myapp. Apphost 包含用於查詢執行時、載入它並使用該執行時啟動您的應用程式的程式碼。Superhost 仍然執行其中一些任務,但使用所有CoreCLR 本機二進位制檔案的靜態連結副本。靜態連結是我們用來實現單一檔案體驗的方法。本機依賴項(如NuGet 包附帶的)是單檔案嵌入的顯著例外。預設情況下,它們不包含在單個檔案中。例如,WPF 本機依賴項不是超級主機的一部分,因此會在單檔案應用程式之外產生其他檔案。您可以使用該設定IncludeNativeLibrariesForSelfExtract嵌入和提取本機依賴項。

靜態分析

我們改進了單檔案分析器以允許自定義警告。如果您的API 在單檔案釋出中不起作用,您現在可以使用[RequiresAssemblyFiles]屬性對其進行標記,如果啟用了分析器,則會出現警告。新增該屬性還將使方法中與單個檔案相關的所有警告靜音,因此您可以使用該警告將警告向上傳播到您的公共API。

PublishSingleFile 設定為true 時,會自動為exe 專案啟用單檔案分析器,但您也可以通過將 EnableSingleFileAnalysis 設定為true 來為任何專案啟用它。 如果您想支援將庫作為單個檔案應用程式的一部分,這將很有幫助。

在.NET 5 中,我們為單檔案包中行為不同的Assembly.Location和一些其他API新增了警告。

壓縮

單檔案包現在支援壓縮,可以通過將屬性設定EnableCompressionInSingleFiletrue. 在執行時,檔案會根據需要解壓縮到記憶體中。壓縮可以為某些場景節省大量空間。

讓我們看一下與NuGet 包資源管理器一起使用的單個檔案釋出(帶壓縮和不帶壓縮)。

無壓縮: 172 MB

壓縮: 71.6 MB

壓縮會顯著增加應用程式的啟動時間,尤其是在Unix 平臺上。Unix 平臺有一個不能用於壓縮的無拷貝快速啟動路徑。您應該在啟用壓縮後測試您的應用程式,看看額外的啟動成本是否可以接受。

單檔案除錯

目前只能使用平臺偵錯程式(如WinDBG)來除錯單檔案應用程式。我們正在考慮使用更高版本的Visual Studio 2022 新增Visual Studio 除錯。

macOS 上的單檔案簽名

單檔案應用程式現在滿足macOS 上的Apple 公證和簽名要求。具體更改與我們根據離散檔案佈局構建單檔案應用程式的方式有關。

Apple 開始對macOS Catalina 實施新簽名和公證要求。我們一直在與Apple 密切合作,以瞭解需求,並尋找使.NET 等開發平臺能夠在該環境中正常工作的解決方案。我們已經進行了產品更改並記錄了使用者工作流程,以滿足Apple 在最近幾個.NET 版本中的要求。剩下的差距之一是單檔案簽名,這是在macOS 上分發.NET 應用程式的要求,包括在macOS 商店中。

IL 修整

該團隊一直致力於為多個版本進行IL 修整。.NET 6 代表了這一旅程向前邁出的重要一步。我們一直在努力使更激進的修剪模式安全且可預測,因此有信心將其設為預設模式。TrimMode=link以前是可選功能,現在是預設功能。

我們有一個三管齊下的修剪策略:

  • 提高平臺的修剪能力。
  • 對平臺進行註釋以提供更好的警告並使其他人也能這樣做。
  • 在此基礎上,讓預設的修剪模式更具侵略性,以便讓應用程式變小。

由於使用未註釋反射的應用程式的結果不可靠,修剪之前一直處於預覽狀態。有了修剪警告,體驗現在應該是可預測的。沒有修剪警告的應用程式應該正確修剪並且在執行時觀察到行為沒有變化。目前,只有核心的.NET 庫已經完全註解了修剪,但我們希望看到生態系統註釋修剪併相容修剪

減小應用程式大小

讓我們使用SDK 工具之一的crossgen來看看這個修剪改進。它可以通過幾個修剪警告進行修剪,crossgen 團隊能夠解決。

首先,讓我們看一下將crossgen 釋出為一個獨立的應用程式而無需修剪。它是80 MB(包括.NET 執行時和所有庫)。

然後我們可以嘗試(現在是舊版).NET 5 預設修剪模式,copyused. 結果降至55 MB。

新的.NET 6 預設修剪模式link將獨立檔案大小進一步降低到36MB。

我們希望新的link修剪模式能更好地與修剪的期望保持一致:顯著節省和可預測的結果。

預設啟用警告

修剪警告告訴您修剪可能會刪除執行時使用的程式碼的地方。這些警告以前預設禁用,因為警告非常嘈雜,主要是由於 .NET 平臺沒有參與修剪作為第一類場景。

我們對大部分 .NET 庫進行了註釋,以便它們產生準確的修剪警告。因此,我們覺得是時候預設啟用修剪警告了。ASP.NET Core 和 Windows 桌面執行時庫尚未註釋。我們計劃接下來註釋 ASP.NET 服務元件(在 .NET 6 之後)。我們希望看到社群在 .NET 6 釋出後對 NuGet 庫進行註釋。

您可以通過設定<SuppressTrimAnalysisWarnings>true來禁用警告。

更多資訊:

與本機 AOT 共享

我們也為Native AOT實驗實現了相同的修剪警告,這應該會以幾乎相同的方式改善 Native AOT 編譯體驗。

數學

我們顯著改進了數學 API。社群中的一些人已經在享受這些改進

面向效能的 API

System.Math 中新增了面向效能的數學 API。如果底層硬體支援,它們的實現是硬體加速的。

新 API:

  • SinCos用於同時計算SinCos
  • ReciprocalEstimate用於計算 1 / x的近似值。
  • ReciprocalSqrtEstimate用於計算1 / Sqrt(x)的近似值。

新的過載:

  • Clamp, DivRem,MinMax支援nintnuint
  • AbsSign支援nint
  • DivRem 變體返回tuple

效能改進:

大整數效能

改進了從十進位制和十六進位制字串中解析 BigIntegers。我們看到了高達89% 的改進,如下圖所示(越低越好)。

感謝約瑟夫·達席爾瓦

Complex API 現在註釋為 readonly

現在對各種API 進行了註釋,System.Numerics.Complexreadonly以確保不會對readonly值或傳遞的值進行復制in。

歸功於hrrrrustic 。

BitConverter 現在支援浮點到無符號整數位廣播

BitConverter 現在支援DoubleToUInt64Bits, HalfToUInt16Bits, SingleToUInt32Bits, UInt16BitsToHalf, UInt32BitsToSingle, 和UInt64BitsToDouble. 這應該使得在需要時更容易進行浮點位操作。

歸功於Michal Petryka 。

BitOperations 支援附加功能

BitOperations現在支援IsPow2,RoundUpToPowerOf2提供nint/nuint過載現有函式

感謝約翰凱利霍耀源羅賓林德納

Vector<T>, Vector2, Vector3 和 Vector4 改進

Vector<T>現在支援C# 9 中新增的原始型別nint和nuint原始型別。例如,此更改應該可以更簡單地使用帶有指標或平臺相關長度型別的SIMD 指令。

Vector<T>現在支援一種Sum方法來簡化計算向量中所有元素的“水平和”的需要。歸功於伊萬茲拉塔諾夫

Vector<T>現在支援一種通用方法As<TFrom, TTo>來簡化在具體型別未知的通用上下文中處理向量。感謝霍耀源

過載支援Span<T>已新增到Vector2Vector3Vector4以改善需要載入或儲存向量型別時的體驗。

更好地解析標準數字格式

我們改進了標準數字型別的解析器,特別是.ToString.TryFormatParse。他們現在將理解對精度 >99 位小數的要求,並將為那麼多位數提供準確的結果。此外,解析器現在更好地支援方法中的尾隨零。

以下示例演示了之前和之後的行為。

  • 32.ToString("C100")->C132

    • .NET 6:<details>
      </details>$32.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    • .NET 5:我們在格式化程式碼中人為限制只能處理 <= 99 的精度。對於精度 >= 100,我們改為將輸入解釋為自定義格式。
  • 32.ToString("H99")-> 扔一個FormatException

    • .NET 6:丟擲 FormatException
    • 這是正確的行為,但在這裡呼叫它是為了與下一個示例進行對比。
  • 32.ToString("H100")->H132

    • .NET 6:丟擲FormatException
    • .NET 5:H是無效的格式說明符。所以,我們應該丟擲一個FormatException. 相反,我們將精度>= 100 解釋為自定義格式的錯誤行為意味著我們返回了錯誤的值。
  • double.Parse("9007199254740997.0")->9007199254740998

    • .NET 6 9007199254740996:。
    • .NET 5:9007199254740997.0不能完全以IEEE 754 格式表示。使用我們當前的舍入方案,正確的返回值應該是9007199254740996. 但是,輸入的最後一部分迫使解析器錯誤地舍入結果並返回。.09007199254740998

System.Text.Json

System.Text.Json提供多種高效能API 用於處理JSON 文件。在過去的幾個版本中,我們新增了新功能,以進一步提高JSON 處理效能並減輕對希望從NewtonSoft.Json遷移的人的阻礙。 此版本包括在該路徑上的繼續,並且在效能方面向前邁出了一大步,特別是在序列化程式源生成器方面。

JsonSerializer 源生成

注意:使用.NET 6 RC1 或更早版本的原始碼生成的應用程式應重新編譯

幾乎所有.NET 序列化程式的支柱都是反射。反射對於某些場景來說是一種很好的能力,但不能作為高效能雲原生應用程式(通常(反)序列化和處理大量JSON 文件)的基礎。反射是啟動、記憶體使用和程式集修整的問題。

執行時反射的替代方法是編譯時原始碼生成。在.NET 6 中,我們包含一個新的原始碼生成器作為 System.Text.Json. JSON 原始碼生成器可以與多種方式結合使用JsonSerializer並且可以通過多種方式進行配置。

它可以提供以下好處:

  • 減少啟動時間
  • 提高序列化吞吐量
  • 減少私有記憶體使用
  • 刪除執行時使用System.ReflectionSystem.Reflection.Emit
  • IL 修整相容性

預設情況下,JSON 源生成器為給定的可序列化型別發出序列化邏輯。JsonSerializer通過生成直接使用的原始碼,這提供了比使用現有方法更高的效能Utf8JsonWriter。簡而言之,原始碼生成器提供了一種在編譯時為您提供不同實現的方法,以使執行時體驗更好。

給定一個簡單的型別:

namespace Test
{
    internal class JsonMessage
    {
        public string Message { get; set; }
    }
}

源生成器可以配置為為示例JsonMessage型別的例項生成序列化邏輯。請注意,類名JsonContext是任意的。您可以為生成的源使用所需的任何類名。

using System.Text.Json.Serialization;

namespace Test
{
    [JsonSerializable(typeof(JsonMessage)]
    internal partial class JsonContext : JsonSerializerContext
    {
    }
}

使用此模式的序列化程式呼叫可能類似於以下示例。此示例提供了可能的最佳效能。

using MemoryStream ms = new();
using Utf8JsonWriter writer = new(ms);

JsonSerializer.Serialize(jsonMessage, JsonContext.Default.JsonMessage);
writer.Flush();

// Writer contains:
// {"Message":"Hello, world!"}

最快和最優化的原始碼生成模式——基於Utf8JsonWriter——目前僅可用於序列化。Utf8JsonReader根據您的反饋,將來可能會提供對反序列化的類似支援。

源生成器還發出型別後設資料初始化邏輯,這也有利於反序列化。JsonMessage要反序列化使用預生成型別後設資料的例項,您可以執行以下操作:

JsonSerializer.Deserialize(json, JsonContext.Default.JsonMessage);

**JsonSerializer 支援 IAsyncEnumerable

您現在可以使用System.Text.Json(反)序列化IAsyncEnumerable<T>JSON 陣列。以下示例使用流作為任何非同步資料來源的表示。源可以是本地計算機上的檔案,也可以是資料庫查詢或Web 服務API 呼叫的結果。

JsonSerializer.SerializeAsync已更新以識別併為IAsyncEnumerable值提供特殊處理。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;

static async IAsyncEnumerable<int> PrintNumbers(int n)
{
    for (int i = 0; i < n; i++) yield return i;
}

using Stream stream = Console.OpenStandardOutput();
var data = new { Data = PrintNumbers(3) };
await JsonSerializer.SerializeAsync(stream, data); // prints {"Data":[0,1,2]}

IAsyncEnumerable僅使用非同步序列化方法支援值。嘗試使用同步方法進行序列化將導致NotSupportedException被丟擲。

流式反序列化需要一個新的 API 來返回IAsyncEnumerable<T>。我們為此新增了JsonSerializer.DeserializeAsyncEnumerable方法,您可以在以下示例中看到。

using System;
using System.IO;
using System.Text;
using System.Text.Json;

var stream = new MemoryStream(Encoding.UTF8.GetBytes("[0,1,2,3,4]"));
await foreach (int item in JsonSerializer.DeserializeAsyncEnumerable<int>(stream))
{
    Console.WriteLine(item);
}

此示例將按需反序列化元素,並且在使用特別大的資料流時非常有用。它僅支援從根級JSON 陣列讀取,儘管將來可能會根據反饋放寬。

現有DeserializeAsync方法名義上支援IAsyncEnumerable<T>,但在其非流方法簽名的範圍內。它必須將最終結果作為單個值返回,如以下示例所示。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;

var stream = new MemoryStream(Encoding.UTF8.GetBytes(@"{""Data"":[0,1,2,3,4]}"));
var result = await JsonSerializer.DeserializeAsync<MyPoco>(stream);
await foreach (int item in result.Data)
{
    Console.WriteLine(item);
}

public class MyPoco
{
    public IAsyncEnumerable<int> Data { get; set; }
}

在此示例中,反序列化器將IAsyncEnumerable在返回反序列化物件之前緩衝記憶體中的所有內容。這是因為反序列化器需要在返回結果之前消耗整個 JSON 值。

System.Text.Json:可寫 DOM 功能

可寫JSON DOM 特性System.Text.Json新增了一個新的簡單且高效能的程式設計模型。這個新的API 很有吸引力,因為它避免了需要強型別的序列化合約,並且與現有的JsonDocument型別相比,DOM 是可變的。

這個新的 API 有以下好處:

  • 在使用POCO型別是不可能或不希望的情況下,或者當JSON 模式不固定且必須檢查的情況下,序列化的輕量級替代方案。
  • 啟用對大樹子集的有效修改。例如,可以有效地導航到大型JSON 樹的子部分並從該子部分讀取陣列或反序列化POCO。LINQ 也可以與它一起使用。

以下示例演示了新的程式設計模型。

    // Parse a JSON object
    JsonNode jNode = JsonNode.Parse("{"MyProperty":42}");
    int value = (int)jNode["MyProperty"];
    Debug.Assert(value == 42);
    // or
    value = jNode["MyProperty"].GetValue<int>();
    Debug.Assert(value == 42);

    // Parse a JSON array
    jNode = JsonNode.Parse("[10,11,12]");
    value = (int)jNode[1];
    Debug.Assert(value == 11);
    // or
    value = jNode[1].GetValue<int>();
    Debug.Assert(value == 11);

    // Create a new JsonObject using object initializers and array params
    var jObject = new JsonObject
    {
        ["MyChildObject"] = new JsonObject
        {
            ["MyProperty"] = "Hello",
            ["MyArray"] = new JsonArray(10, 11, 12)
        }
    };

    // Obtain the JSON from the new JsonObject
    string json = jObject.ToJsonString();
    Console.WriteLine(json); // {"MyChildObject":{"MyProperty":"Hello","MyArray":[10,11,12]}}

    // Indexers for property names and array elements are supported and can be chained
Debug.Assert(jObject["MyChildObject"]["MyArray"][1].GetValue<int>() == 11);

ReferenceHandler.IgnoreCycles

JsonSerializer(System.Text.Json)現在支援在序列化物件圖時忽略迴圈的能力。該ReferenceHandler.IgnoreCycles選項具有與Newtonsoft.Json ReferenceLoopHandling.Ignore類似的行為。一個關鍵區別是System.Text.Json 實現用null JSON 標記替換引用迴圈,而不是忽略物件引用。

您可以在以下示例中看到ReferenceHandler.IgnoreCycles的行為。在這種情況下,該Next屬性被序列化為null,因為否則它會建立一個迴圈。

class Node
{
    public string Description { get; set; }
    public object Next { get; set; }
}

void Test()
{
    var node = new Node { Description = "Node 1" };
    node.Next = node;

    var opts = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles };

    string json = JsonSerializer.Serialize(node, opts);
    Console.WriteLine(json); // Prints {"Description":"Node 1","Next":null}
}

原始碼構建

通過原始碼構建,您只需幾個命令即可在您自己的計算機上從原始碼構建.NET SDK 。讓我解釋一下為什麼這個專案很重要。

原始碼構建是一個場景,也是我們在釋出.NET Core 1.0 之前一直與Red Hat 合作開發的基礎架構。幾年後,我們非常接近於交付它的全自動版本。對於Red Hat Enterprise Linux (RHEL) .NET 使用者來說,這個功能很重要。Red Hat 告訴我們,.NET 已經發展成為其生態系統的重要開發者平臺。好的!

Linux 發行版的黃金標準是使用作為發行版存檔一部分的編譯器和工具鏈構建開原始碼。這適用於.NET 執行時(用C++ 編寫),但不適用於任何用C# 編寫的程式碼。對於C# 程式碼,我們使用兩遍構建機制來滿足發行版要求。這有點複雜,但瞭解流程很重要。

Red Hat 使用.NET SDK (#1) 的Microsoft 二進位制構建來構建.NET SDK 原始碼,以生成SDK (#2) 的純開源二進位制構建。之後,使用這個新版本的SDK (#2) 再次構建相同的SDK 原始碼,以生成可證明的開源SDK (#3)。.NET SDK (#3) 的最終二進位制版本隨後可供RHEL 使用者使用。之後,Red Hat 可以使用相同的SDK (#3) 來構建新的.NET 版本,而不再需要使用Microsoft SDK 來構建每月更新。

這個過程可能令人驚訝和困惑。開源發行版需要通過開源工具構建。此模式確保不需要Microsoft 構建的SDK,無論是有意還是無意。作為開發者平臺,包含在發行版中的門檻比僅使用相容許可證的門檻更高。原始碼構建專案使.NET 能夠滿足該標準。

原始碼構建的可交付成果是原始碼壓縮包。源tarball 包含SDK 的所有源(對於給定版本)。從那裡,紅帽(或其他組織)可以構建自己的SDK 版本。Red Hat 政策要求使用內建源工具鏈來生成二進位制tar 球,這就是他們使用兩遍方法的原因。但是原始碼構建本身不需要這種兩遍方法。

在Linux 生態系統中,給定元件同時擁有源和二進位制包或tarball 是很常見的。我們已經有了可用的二進位制tarball,現在也有了源tarball。這使得.NET 與標準元件模式相匹配。

.NET 6 的重大改進是源tarball 現在是我們構建的產品。它過去需要大量的人工來製作,這也導致將源tarball 交付給Red Hat 的延遲很長。雙方都對此不滿意。

在這個專案上,我們與紅帽密切合作五年多。它的成功在很大程度上要歸功於我們有幸與之共事的優秀紅帽工程師的努力。其他發行版和組織已經並將從他們的努力中受益。

附帶說明一下,原始碼構建是朝著可重現構建邁出的一大步,我們也堅信這一點。.NET SDK 和C# 編譯器具有重要的可重現構建功能。

庫 API

除了已經涵蓋的API 之外,還新增了以下API。

WebSocket 壓縮

壓縮對於通過網路傳輸的任何資料都很重要。WebSockets 現在啟用壓縮。我們使用了WebSockets 的擴充套件permessage-deflate實現,RFC 7692。它允許使用該DEFLATE演算法壓縮WebSockets 訊息負載。此功能是GitHub 上Networking 的主要使用者請求之一。

與加密一起使用的壓縮可能會導致攻擊,例如CRIMEBREACH。這意味著不能在單個壓縮上下文中將祕密與使用者生成的資料一起傳送,否則可以提取該祕密。為了讓使用者注意到這些影響並幫助他們權衡風險,我們將其中一個關鍵API 命名為DangerousDeflateOptions。我們還新增了關閉特定訊息壓縮的功能,因此如果使用者想要傳送祕密,他們可以在不壓縮的情況下安全地執行此操作。

禁用壓縮時WebSocket的記憶體佔用減少了約27%。

從客戶端啟用壓縮很容易,如下例所示。但是,請記住,伺服器可以協商設定,例如請求更小的視窗或完全拒絕壓縮。

var cws = new ClientWebSocket();
cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions()
{
    ClientMaxWindowBits = 10,
    ServerMaxWindowBits = 10
};

還新增了對 ASP.NET Core 的 WebSocket 壓縮支援。

歸功於伊萬茲拉塔諾夫

Socks 代理支援

SOCKS是一種代理伺服器實現,可以處理任何TCP 或UDP 流量,使其成為一個非常通用的系統。這是一個長期存在的社群請求,已新增到.NET 6中。

此更改增加了對Socks4、Socks4a 和Socks5 的支援。例如,它可以通過SSH 測試外部連線或連線到 Tor 網路

該類WebProxy現在接受socks方案,如以下示例所示。

var handler = new HttpClientHandler
{
    Proxy = new WebProxy("socks5://127.0.0.1", 9050)
};
var httpClient = new HttpClient(handler);

歸功於Huo yaoyuan。

Microsoft.Extensions.Hosting — 配置主機選項 API

我們在IHostBuilder 上新增了一個新的ConfigureHostOptions API,以簡化應用程式設定(例如,配置關閉超時):

using HostBuilder host = new()
    .ConfigureHostOptions(o =>
    {
        o.ShutdownTimeout = TimeSpan.FromMinutes(10);
    })
    .Build();

host.Run();

在.NET 5 中,配置主機選項有點複雜:

using HostBuilder host = new()
    .ConfigureServices(services =>
    {
        services.Configure<HostOptions>(o =>
        {
            o.ShutdownTimeout = TimeSpan.FromMinutes(10);
        });
    })
    .Build();

host.Run();

Microsoft.Extensions.DependencyInjection — CreateAsyncScope API

CreateAsyncScope建立API是為了處理服務的處置IAsyncDisposable。以前,您可能已經注意到處置IAsyncDisposable服務提供者可能會引發InvalidOperationException異常。

以下示例演示了新模式,CreateAsyncScope用於啟用using語句的安全使用。

await using (var scope = provider.CreateAsyncScope())
{
    var foo = scope.ServiceProvider.GetRequiredService<Foo>();
}

以下示例演示了現有的問題案例:

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

await using var provider = new ServiceCollection()
        .AddScoped<Foo>()
        .BuildServiceProvider();

// This using can throw InvalidOperationException
using (var scope = provider.CreateScope())
{
    var foo = scope.ServiceProvider.GetRequiredService<Foo>();
}

class Foo : IAsyncDisposable
{
    public ValueTask DisposeAsync() => default;
}

以下模式是先前建議的避免異常的解決方法。不再需要它。

var scope = provider.CreateScope();
var foo = scope.ServiceProvider.GetRequiredService<Foo>();
await ((IAsyncDisposable)scope).DisposeAsync();

感謝Martin Björkström 。

Microsoft.Extensions.Logging — 編譯時源生成器

.NET 6 引入了LoggerMessageAttribute型別。 此屬性是Microsoft.Extensions.Logging名稱空間的一部分,使用時,它會源生成高效能日誌記錄API。源生成日誌支援旨在為現代.NET 應用程式提供高度可用和高效能的日誌解決方案。自動生成的原始碼依賴於ILogger介面和LoggerMessage.Define功能。

LoggerMessageAttribute源生成器在用於partial日誌記錄方法時觸發。當被觸發時,它要麼能夠自動生成partial它正在裝飾的方法的實現,要麼生成編譯時診斷,並提供有關正確使用的提示。編譯時日誌記錄解決方案在執行時通常比現有的日誌記錄方法快得多。它通過最大限度地消除裝箱、臨時分配和副本來實現這一點。

與直接手動使用LoggerMessage.Define API相比,有以下好處:

  • 更短更簡單的語法:宣告性屬性使用而不是編碼樣板。
  • 引導式開發人員體驗:生成器發出警告以幫助開發人員做正確的事情。
  • 支援任意數量的日誌記錄引數。LoggerMessage.Define最多支援六個。
  • 支援動態日誌級別。這是LoggerMessage.Define單獨不可能的。

要使用LoggerMessageAttribute,消費類和方法需要是partial。程式碼生成器在編譯時觸發並生成partial方法的實現。

public static partial class Log
{
    [LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Could not open socket to `{hostName}`")]
    public static partial void CouldNotOpenSocket(ILogger logger, string hostName);
}

在前面的示例中,日誌記錄方法是static,並且在屬性定義中指定了日誌級別。在靜態上下文中使用屬性時,ILogger需要例項作為引數。您也可以選擇在非靜態上下文中使用該屬性。有關更多示例和使用場景,請訪問編譯時日誌記錄源生成器文件。

System.Linq — 可列舉的支援 Index 和 Range 引數

Enumerable.ElementAt方法現在接受來自可列舉末尾的索引,如以下示例所示。

Enumerable.Range(1, 10).ElementAt(^2); // returns 9

新增了一個Enumerable.Take接受Range引數的過載。它簡化了對可列舉序列的切片:

  • source.Take(..3)代替source.Take(3)
  • source.Take(3..)代替source.Skip(3)
  • source.Take(2..7)代替source.Take(7).Skip(2)
  • source.Take(^3..)代替source.TakeLast(3)
  • source.Take(..^3)代替source.SkipLast(3)
  • source.Take(^7..^3)而不是.source.TakeLast(7).SkipLast(3)

感謝@dixin 。

System.Linq — TryGetNonEnumeratedCount

TryGetNonEnumeratedCount方法嘗試在不強制列舉的情況下獲取源可列舉的計數。這種方法在列舉之前預分配緩衝區很有用的場景中很有用,如下面的示例所示。

List<T> buffer = source.TryGetNonEnumeratedCount(out int count) ? new List<T>(capacity: count) : new List<T>();
foreach (T item in source)
{
    buffer.Add(item);
}

TryGetNonEnumeratedCount檢查實現ICollection/ ICollection<T>;或利用Linq 採用的一些內部優化的源

System.Linq — DistinctBy / UnionBy / IntersectBy / ExceptBy

新變體已新增到允許使用鍵選擇器函式指定相等性的集合操作中,如下例所示。

Enumerable.Range(1, 20).DistinctBy(x => x % 3); // {1, 2, 3}

var first = new (string Name, int Age)[] { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) };
var second = new (string Name, int Age)[] { ("Claire", 30), ("Pat", 30), ("Drew", 33) };
first.UnionBy(second, person => person.Age); // { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40), ("Drew", 33) }

System.Linq - MaxBy / MinBy

MaxByMinBy方法允許使用鍵選擇器查詢最大或最小元素,如下例所示。

var people = new (string Name, int Age)[] { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) };
people.MaxBy(person => person.Age); // ("Ashley", 40)

System.Linq — Chunk

Chunk可用於將可列舉的源分塊為固定大小的切片,如下例所示。

IEnumerable<int[]> chunks = Enumerable.Range(0, 10).Chunk(size: 3); // { {0,1,2}, {3,4,5}, {6,7,8}, {9} }

歸功於羅伯特安德森

System.Linq—— // FirstOrDefault 採用預設引數的過載 LastOrDefaultSingleOrDefault

如果源可列舉為空,則現有的FirstOrDefault /LastOrDefault /SingleOrDefault方法返回default(T)。新增了新的過載,它們接受在這種情況下返回的預設引數,如以下示例所示。

Enumerable.Empty\&lt;int\&gt;().SingleOrDefault(-1); // returns -1

感謝@ Foxtrek64 。

System.Linq — Zip 接受三個可列舉的過載

Zip方法現在支援組合三個列舉,如以下示例所示。

var xs = Enumerable.Range(1, 10);
var ys = xs.Select(x => x.ToString());
var zs = xs.Select(x => x % 2 == 0);

foreach ((int x, string y, bool z) in Enumerable.Zip(xs,ys,zs))
{
}

歸功於Huo yaoyuan。

優先佇列

PriorityQueue<TElement, TPriority>(System.Collections.Generic) 是一個新集合,可以新增具有值和優先順序的新專案。在出隊時,PriorityQueue 返回具有最低優先順序值的元素。您可以認為這個新集合類似於Queue<T>但每個入隊元素都有一個影響出隊行為的優先順序值。

以下示例演示了.PriorityQueue<string, int>

// creates a priority queue of strings with integer priorities
var pq = new PriorityQueue<string, int>();

// enqueue elements with associated priorities
pq.Enqueue("A", 3);
pq.Enqueue("B", 1);
pq.Enqueue("C", 2);
pq.Enqueue("D", 3);

pq.Dequeue(); // returns "B"
pq.Dequeue(); // returns "C"
pq.Dequeue(); // either "A" or "D", stability is not guaranteed.

歸功於Patryk Golebiowski

更快地將結構處理為字典值

CollectionsMarshal.GetValueRef是一個新的 不安全 API,它可以更快地更新字典中的結構值。新API 旨在用於高效能場景,而不是用於一般用途。它返回ref結構值,然後可以使用典型技術對其進行更新。

以下示例演示瞭如何使用新API:

ref MyStruct value = CollectionsMarshal.GetValueRef(dictionary, key);
// Returns Unsafe.NullRef<TValue>() if it doesn't exist; check using Unsafe.IsNullRef(ref value)
if (!Unsafe.IsNullRef(ref value))
{
    // Mutate in-place
    value.MyInt++;
}

在此更改之前,更新struct字典值對於高效能場景可能會很昂貴,需要字典查詢和複製到堆疊的struct. 然後在更改之後struct,它將再次分配給字典鍵,從而導致另一個查詢和複製操作。這種改進將金鑰雜湊減少到1(從2)並刪除了所有結構複製操作。

歸功於本亞當斯

新建 DateOnly 和 TimeOnly 結構

新增了僅限日期和時間的結構,具有以下特徵:

  • 每個都代表a 的一半DateTime,或者只是日期部分,或者只是時間部分。
  • DateOnly非常適合生日、週年紀念日和工作日。它與SQL Server 的date型別一致。
  • TimeOnly非常適合定期會議、鬧鐘和每週工作時間。它與SQL Server 的time型別一致。
  • 補充現有的日期/時間型別( DateTime, DateTimeOffset, TimeSpan, TimeZoneInfo)。
  • System名稱空間中,在CoreLib 中提供,就像現有的相關型別一樣。

效能改進 DateTime.UtcNow

這種改進具有以下好處:

  • 修復了在Windows 上獲取系統時間的2.5 倍效能迴歸。
  • 利用Windows 閏秒資料的5 分鐘滑動快取,而不是在每次呼叫時獲取。

在所有平臺上支援 Windows 和 IANA 時區

這種改進具有以下好處:

  • 使用時的隱式轉換(https://github.com/dotnet/run...TimeZoneInfo.FindSystemTimeZoneById
  • TimeZoneInfo通過: TryConvertIanaIdToWindowsIdTryConvertWindowsIdToIanaIdHasIanaId(https://github.com/dotnet/run...)上的新API 進行顯式轉換
  • 改進了使用不同時區型別的系統之間的跨平臺支援和互操作。
  • 刪除需要使用TimeZoneConverter OSS 庫。該功能現在是內建的。

改進的時區顯示名稱

Unix 上的時區顯示名稱已得到改進

  • 消除由.返回的列表中的顯示名稱的歧義。TimeZoneInfo.GetSystemTimeZones
  • 利用ICU / CLDR 全球化資料。
  • 僅適用於Unix。Windows 仍然使用登錄檔資料。這可能會在以後更改。

還進行了以下附加改進:

  • UTC 時區的顯示名稱和標準名稱被硬編碼為英語,現在使用與其餘時區資料相同的語言(CurrentUICulture在Unix 上,Windows 上的作業系統預設語言)。
  • 由於大小限制,Wasm 中的時區顯示名稱改為使用非本地化IANA ID。
  • TimeZoneInfo.AdjustmentRule巢狀類將其BaseUtcOffsetDelta內部屬性公開,並獲得一個新的建構函式,該建構函式baseUtcOffsetDelta作為引數。(https://github.com/dotnet/run...
  • TimeZoneInfo.AdjustmentRule還獲得了在Unix 上載入時區的各種修復(https://github.com/dotnet/run...), (https://github.com/dotnet/run...)

改進了對 Windows ACL 的支援

System.Threading.AccessControl現在包括對與Windows 訪問控制列表(ACL) 互動的改進支援。新的過載被新增到MutexSemaphoreOpenExistingTryOpenExisting方法EventWaitHandle中。這些具有“安全許可權”例項的過載允許開啟使用特殊Windows 安全屬性建立的執行緒同步物件的現有例項。

此更新與.NET Framework 中可用的API 匹配並且具有相同的行為。

以下示例演示瞭如何使用這些新API。

對於Mutex

var rights = MutexRights.FullControl;
string mutexName = "MyMutexName";

var security = new MutexSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
MutexAccessRule accessRule = new MutexAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);

// createdMutex, openedMutex1 and openedMutex2 point to the same mutex
Mutex createdMutex = MutexAcl.Create(initiallyOwned: true, mutexName, out bool createdNew, security);
Mutex openedMutex1 = MutexAcl.OpenExisting(mutexName, rights);
MutexAcl.TryOpenExisting(mutexName, rights, out Mutex openedMutex2);

為了Semaphore

var rights = SemaphoreRights.FullControl;
string semaphoreName = "MySemaphoreName";

var security = new SemaphoreSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
SemaphoreAccessRule accessRule = new SemaphoreAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);

// createdSemaphore, openedSemaphore1 and openedSemaphore2 point to the same semaphore
Semaphore createdSemaphore = SemaphoreAcl.Create(initialCount: 1,  maximumCount: 3, semaphoreName, out bool createdNew, security);
Semaphore openedSemaphore1 = SemaphoreAcl.OpenExisting(semaphoreName, rights);
SemaphoreAcl.TryOpenExisting(semaphoreName, rights, out Semaphore openedSemaphore2);

為了EventWaitHandle

var rights = EventWaitHandleRights.FullControl;
string eventWaitHandleName = "MyEventWaitHandleName";

var security = new EventWaitHandleSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
EventWaitHandleAccessRule accessRule = new EventWaitHandleAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);

// createdHandle, openedHandle1 and openedHandle2 point to the same event wait handle
EventWaitHandle createdHandle = EventWaitHandleAcl.Create(initialState: true, EventResetMode.AutoReset, eventWaitHandleName, out bool createdNew, security);
EventWaitHandle openedHandle1 = EventWaitHandleAcl.OpenExisting(eventWaitHandleName, rights);
EventWaitHandleAcl.TryOpenExisting(eventWaitHandleName, rights, out EventWaitHandle openedHandle2);

HMAC 一次性方法

System.Security.CryptographyHMAC類現在具有允許一次性計算HMAC而無需分配的靜態方法。這些新增類似於在先前版本中新增的用於雜湊生成的一次性方法。

DependentHandle 現已公開

DependentHandle型別現在是公共的,具有以下 API 表面

namespace System.Runtime
{
    public struct DependentHandle : IDisposable
    {
        public DependentHandle(object? target, object? dependent);
        public bool IsAllocated { get; }
        public object? Target { get; set; }
        public object? Dependent { get; set; }
        public (object? Target, object? Dependent) TargetAndDependent { get; }
        public void Dispose();
    }
}

它可用於建立高階系統,例如複雜的快取系統或ConditionalWeakTable<TKey, TValue>型別​​的自定義版本。例如,它將被MVVM Toolkit中的WeakReferenceMessenger型別使用,以避免在廣播訊息時分配記憶體。

可移植執行緒池

.NET 執行緒池已作為託管實現重新實現,現在用作.NET 6 中的預設執行緒池。我們進行此更改以使所有.NET 應用程式都可以訪問同一個執行緒池,而不管是否正在使用CoreCLR、Mono 或任何其他執行時。作為此更改的一部分,我們沒有觀察到或預期任何功能或效能影響。

RyuJIT

該團隊在此版本中對.NET JIT 編譯器進行了許多改進,在每個預覽帖子中都有記錄。這些更改中的大多數都提高了效能。這裡介紹了一些RyuJIT 的亮點。

動態 PGO

在.NET 6 中,我們啟用了兩種形式的PGO(配置檔案引導優化):

  • 動態 PGO使用從當前執行中收集的資料來優化當前執行。
  • 靜態PGO依靠從過去執行中收集的資料來優化未來執行。

動態PGO 已經在文章前面的效能部分中介紹過。我將提供一個重新上限。

動態PGO 使JIT 能夠在執行時收集有關實際用於特定應用程式執行的程式碼路徑和型別的資訊。然後,JIT 可以根據這些程式碼路徑優化程式碼,有時會顯著提高效能。我們在測試和生產中都看到了兩位數的健康改進。有一組經典的編譯器技術在沒有PGO 的情況下使用JIT 或提前編譯都無法實現。我們現在能夠應用這些技術。熱/冷分離是一種這樣的技術,而去虛擬化是另一種技術。

要啟用動態PGO,請在應用程式將執行的環境中進行設定DOTNET\_TieredPGO=1

如效能部分所述,動態PGO 將TechEmpower JSON"MVC"套件每秒的請求數提高了26%(510K -\&gt; 640K)。這是一個驚人的改進,無需更改程式碼。

我們的目標是在未來的.NET 版本中預設啟用動態PGO,希望在.NET 7 中啟用。我們強烈建議您在應用程式中嘗試動態PGO 並向我們提供反饋。

完整的 PGO

要充分利用Dynamic PGO,您可以設定兩個額外的環境變數:DOTNET\_TC\_QuickJitForLoops=1DOTNET\_ReadyToRun=0。 這確保了儘可能多的方法參與分層編譯。我們將此變體稱為 Full PGO 。與動態PGO 相比,完整PGO 可以提供更大的穩態效能優勢,但啟動時間會更慢(因為必須在第0 層執行更多方法)。

您不希望將此選項用於短期執行的無伺服器應用程式,但對於長期執行的應用程式可能有意義。

在未來的版本中,我們計劃精簡和簡化這些選項,以便您可以更簡單地獲得完整PGO 的好處並用於更廣泛的應用程式。

靜態 PGO

我們目前使用 靜態 PGO 來優化.NET 庫程式集,例如R2R(Ready To Run)附帶的程式集System.Private.CoreLib

靜態PGO 的好處是,在使用crossgen 將程式集編譯為R2R 格式時會進行優化。這意味著有執行時的好處而沒有執行時成本。這是非常重要的,也是PGO 對C++ 很重要的原因,例如。

迴圈對齊

記憶體對齊是現代計算中各種操作的共同要求。在.NET 5 中,我們開始在 32 位元組邊界對齊方法。在.NET 6 中,我們新增了一項執行自適應迴圈對齊的功能,該功能在具有迴圈的方法中新增NOP填充指令,以便迴圈程式碼從mod(16) 或mod(32) 記憶體地址開始。這些更改改進並穩定了.NET 程式碼的效能。

在下面的氣泡排序圖中,資料點1 表示我們開始在32 位元組邊界對齊方法的點。資料點2 表示我們也開始對齊內部迴圈的點。如您所見,基準測試的效能和穩定性都有很大提高。

硬體加速結構

結構是CLR 型別系統的重要組成部分。近年來,它們經常被用作整個.NET 庫中的效能原語。最近的例子ValueTaskValueTupleSpan<T>。記錄結構是一個新的例子。在.NET 5 和.NET 6 中,我們一直在提高結構的效能,部分原因是通過確保結構是區域性變數、引數或方法的返回值時可以儲存在超快速CPU 暫存器中)。這對於使用向量計算的API 特別有用。

穩定效能測量

團隊中有大量從未出現在部落格上的工程系統工作。這對於您使用的任何硬體或軟體產品都是如此。JIT 團隊開展了一個專案來穩定效能測量,目標是增加我們內部效能實驗室自動化自動報告的迴歸值。這個專案很有趣,因為需要進行深入調查產品更改才能實現穩定性。它還展示了我們為保持和提高績效而衡量的規模。

此影像演示了不穩定的效能測量,其中效能在連續執行中在慢速和快速之間波動。x 軸是測試日期,y 軸是測試時間,以納秒為單位。到圖表末尾(提交這些更改後),您可以看到測量值穩定,結果最好。這張圖片展示了一個單一的測試。還有更多測試在dotnet/runtime #43227中被證明具有類似的行為。

即用型程式碼 /Crossgen 2

Crossgen2 是crossgen 工具的替代品。它旨在滿足兩個結果:

  • 讓crossgen開發更高效。
  • 啟用一組目前無法通過crossgen 實現的功能。

這種轉換有點類似於本機程式碼csc.exe 到託管程式碼Roslyn 編譯器。Crossgen2 是用C# 編寫的,但是它沒有像Roslyn 那樣公開一個花哨的API。

我們可能已經/已經為.NET 6 和7 計劃了六個專案,這些專案依賴於crossgen2。向量指令預設提議是我們希望為.NET 6 但更可能是.NET 7 進行的crossgen2 功能和產品更改的一個很好的例子。版本氣泡是另一個很好的例子。

Crossgen2 支援跨作業系統和架構維度的交叉編譯(因此稱為"crossgen")。這意味著您將能夠使用單個構建機器為所有目標生成本機程式碼,至少與準備執行的程式碼相關。但是,執行和測試該程式碼是另一回事,為此您需要合適的硬體和作業系統。

第一步是用crossgen2編譯平臺本身。我們使用.NET 6 完成了所有架構的任務。因此,我們能夠在此版本中淘汰舊的crossgen。請注意,crossgen2 僅適用於CoreCLR,而不適用於基於Mono 的應用程式(它們具有一組單獨的程式碼生成工具)。

這個專案——至少一開始——並不以效能為導向。目標是啟用更好的架構來託管RyuJIT(或任何其他)編譯器以離線方式生成程式碼(不需要或啟動執行時)。

你可能會說“嘿……如果是用C# 編寫的,難道你不需要啟動執行時來執行crossgen2 嗎?” 是的,但這不是本文中“離線”的含義。當crossgen2 執行時,我們不使用執行crossgen2 的執行時附帶的JIT 來生成準備執行(R2R) 程式碼. 那是行不通的,至少對於我們的目標來說是行不通的。想象一下crossgen2 在x64 機器上執行,我們需要為Arm64 生成程式碼。Crossgen2 將Arm64 RyuJIT(針對x64 編譯)載入為原生外掛,然後使用它生成Arm64 R2R 程式碼。機器指令只是儲存到檔案中的位元組流。它也可以在相反的方向工作。在Arm64 上,crossgen2 可以使用編譯為Arm64 的x64 RyuJIT 生成x64 程式碼。我們使用相同的方法來針對x64 機器上的x64 程式碼。Crossgen2 會載入一個RyuJIT,它是為任何需要的配置而構建的。這可能看起來很複雜,但如果您想啟用無縫的交叉定位模型,它就是您需要的那種系統,而這正是我們想要的。

我們希望只在一個版本中使用術語“crossgen2”,之後它將替換現有的crossgen,然後我們將回到使用術語“crossgen”來表示“crossgen2”。

.NET 診斷:EventPipe

EventPipe 是我們用於在程式內或程式外輸出事件、效能資料和計數器的跨平臺機制。從.NET 6 開始,我們已將實現從C++ 移至C。通過此更改,Mono 也使用EventPipe。這意味著CoreCLR 和Mono 都使用相同的事件基礎設施,包括.NET 診斷CLI 工具。

這一變化還伴隨著CoreCLR 的小幅減小:

大小之後 - 大小之前差異
libcoreclr.so7037856 – 7049408-11552

我們還進行了一些更改,以提高 EventPipe 在負載下的吞吐量。在最初的幾個預覽版中,我們進行了一系列更改,從而使吞吐量提高了.NET 5 的2.06 倍:

對於這個基準,越高越好。.NET 6 是橙色線,.NET 5 是藍色線。

SDK

對.NET SDK 進行了以下改進。

.NET 6 SDK 可選工作負載的 CLI 安裝

.NET 6 引入了SDK 工作負載的概念。工作負載是可選元件,可以安裝在.NET SDK 之上以啟用各種場景。.NET 6 中的新工作負載是:.NET MAUI 和Blazor WebAssembly AOT 工作負載。我們可能會在.NET 7 中建立新的工作負載(可能來自現有的SDK)。工作負載的最大好處是減少大小和可選性。我們希望隨著時間的推移使SDK 變得更小,並且只安裝您需要的元件。這個模型對開發者機器有好處,對CI 來說甚至更好。

Visual Studio 使用者並不真正需要擔心工作負載。工作負載功能經過專門設計,以便像Visual Studio 這樣的安裝協調器可以為您安裝工作負載。可以通過CLI 直接管理工作負載。

工作負載功能公開了用於管理工作負載的多個動詞,包括以下幾個:

  • dotnet workload restore— 安裝給定專案所需的工作負載。
  • dotnet workload install— 安裝命名工作負載。
  • dotnet workload list— 列出您已安裝的工作負載。
  • dotnet workload update— 將所有已安裝的工作負載更新到最新的可用版本。

update動詞查詢更新nuget.org的工作負載清單、更新本地清單、下載已安裝工作負載的新版本,然後刪除所有舊版本的工作負載。這類似於apt update &amp;&amp; apt upgrade -y(用於基於Debian 的Linux 發行版)。將工作負載視為SDK 的私有包管理器是合理的。它是私有的,因為它僅適用於SDK 元件。我們將來可能會重新考慮這一點。這些dotnet workload命令在給定SDK 的上下文中執行。假設您同時安裝了.NET 6 和.NET 7。工作負載命令將為每個SDK 提供不同的結果,因為工作負載將不同(至少相同工作負載的不同版本)。

請注意,將NuGet.org 中的工作負載複製到您的SDK 安裝中,因此如果SDK 安裝位置受到保護(即在管理員/根位置),dotnet workload install則需要執行提升或使用sudo

內建 SDK 版本檢查

為了更容易跟蹤SDK 和執行時的新版本何時可用,我們向.NET 6 SDK 新增了一個新命令。

dotnet sdk check

它會告訴您是否有可用於您已安裝的任何.NET SDK、執行時或工作負載的更新版本。您可以在下圖中看到新體驗。

dotnet new

您現在可以在NuGet.org 中搜尋帶有.dotnet new --search

模板安裝的其他改進包括支援切換以支援私有NuGet 源的授權憑據。--interactive

安裝CLI 模板後,您可以通過和檢查更新是否可用。--update-check--update-apply

NuGet 包驗證

包驗證工具使NuGet 庫開發人員能夠驗證他們的包是否一致且格式正確。

這包括:

  • 驗證版本之間沒有重大更改。
  • 驗證包對於所有特定於執行時的實現是否具有相同的公共API 集。
  • 確定任何目標框架或執行時適用性差距。

該工具是SDK 的一部分。使用它的最簡單方法是在專案檔案中設定一個新屬性。

<EnablePackageValidation> true </EnablePackageValidation>

更多 Roslyn 分析儀

在.NET 5 中,我們提供了大約250 個帶有.NET SDK 的分析器。其中許多已經存在,但作為NuGet 包在帶外傳送。我們為 .NET 6 新增了更多分析器

預設情況下,大多數新分析器都在資訊級別啟用。您可以通過如下配置分析模式在警告級別啟用這些分析器:<AnalysisMode>All</AnalysisMode>

我們為.NET 6 釋出了我們想要的一組分析器(加上一些附加功能),然後將它們中的大多數做成了可供抓取的。社群新增了幾個實現,包括這些。

貢獻者問題標題
紐厄爾·克拉克dotnet/執行時#33777使用基於跨度的string.Concat
紐厄爾·克拉克dotnet/執行時#33784解析時優先string.AsSpan()string.Substring()
紐厄爾·克拉克dotnet/執行時#33789覆蓋Stream.ReadAsync/WriteAsync
紐厄爾·克拉克dotnet/執行時#35343替換為Dictionary\&lt;,\&gt;.Keys.ContainsContainsKey
紐厄爾·克拉克dotnet/執行時#45552使用代替String.EqualsString.Compare
梅克特雷爾dotnet/執行時#47180使用代替String.Contains(char)String.Contains(String)

感謝Meik TranelNewell Clark

為 Platform Compatibility Analyzer 啟用自定義防護

CA1416 平臺相容性分析器已經使用OperatingSystemRuntimeInformation中的方法識別平臺防護,例如OperatingSystem.IsWindowsOperatingSystem.IsWindowsVersionAtLeast。但是,分析器無法識別任何其他保護可能性,例如快取在欄位或屬性中的平臺檢查結果,或者在輔助方法中定義了複雜的平臺檢查邏輯。

為了允許自定義守衛的可能性,我們新增了新屬性 SupportedOSPlatformGuardUnsupportedOSPlatformGuard使用相應的平臺名稱和/或版本註釋自​​定義守衛成員。此註釋被平臺相容性分析器的流分析邏輯識別和尊重。

用法

    [UnsupportedOSPlatformGuard("browser")] // The platform guard attribute
#if TARGET_BROWSER
    internal bool IsSupported => false;
#else
    internal bool IsSupported => true;
#endif

    [UnsupportedOSPlatform("browser")]
    void ApiNotSupportedOnBrowser() { }

    void M1()
    {
        ApiNotSupportedOnBrowser();  // Warns: This call site is reachable on all platforms.'ApiNotSupportedOnBrowser()' is unsupported on: 'browser'

        if (IsSupported)
        {
            ApiNotSupportedOnBrowser();  // Not warn
        }
    }

    [SupportedOSPlatform("Windows")]
    [SupportedOSPlatform("Linux")]
    void ApiOnlyWorkOnWindowsLinux() { }

    [SupportedOSPlatformGuard("Linux")]
    [SupportedOSPlatformGuard("Windows")]
    private readonly bool _isWindowOrLinux = OperatingSystem.IsLinux() || OperatingSystem.IsWindows();

    void M2()
    {
        ApiOnlyWorkOnWindowsLinux();  // This call site is reachable on all platforms.'ApiOnlyWorkOnWindowsLinux()' is only supported on: 'Linux', 'Windows'.

        if (_isWindowOrLinux)
        {
            ApiOnlyWorkOnWindowsLinux();  // Not warn
        }
    }
}

結束

歡迎使用.NET 6。它是另一個巨大的.NET 版本,在效能、功能、可用性和安全性方面都有很多的改進。我們希望您能找到許多改進,最終使您在日常開發中更有效率和能力,並提高效能或降低生產中應用程式的成本。我們已經開始從那些已經開始使用.NET 6 的人那裡聽到好訊息。

在Microsoft,我們還處於.NET 6 部署的早期階段,一些關鍵應用程式已經投入生產,未來幾周和幾個月內還會有更多應用程式推出。

.NET 6 是我們最新的LTS 版本。我們鼓勵每個人都轉向它,特別是如果您使用的是.NET 5。我們期待它成為有史以來採用速度最快的.NET 版本。

此版本是至少1000 人(但可能更多)的結果。這包括來自Microsoft 的.NET 團隊以及社群中的更多人。我試圖在這篇文章中包含許多社群貢獻的功能。感謝您抽出寶貴時間建立這些內容並完成我們的流程。我希望這次經歷是一次美好的經歷,並且更多的人會做出貢獻。

這篇文章是許多有才華的人合作的結果。貢獻包括團隊在整個釋出過程中提供的功能內容、為此最終帖子建立的重要新內容,以及使最終內容達到您應得的質量所需的大量技術和散文更正。很高興為您製作它和所有其他帖子。

感謝您成為.NET 開發人員。

相關文章