點擊上方藍字
關注我們
(本文閱讀時間:20分鐘)
本文翻譯于 Jeremy Likness, Angelos Petropoulos 和 Jon Douglas 的博客
.NET 7 為C# 11/F# 7、.NET MAUI、ASP.NET Core/Blazor、Web API、WinForms、WPF 等應用程序帶來了更高的性能和新功能。使用 .NET 7,您還可以輕松地將 .NET 7 項目容器化,在 GitHub 操作中設置 CI/CD 工作流,并實現云原生可觀察性。歡迎下載 .NET 7!
歡迎下載?.NET 7
https://dotnet.microsoft.com/download/dotnet/7.0
更高的性能:
https://devblogs.microsoft.com/dotnet/performance_improvements_in_net_7/?ocid=AID3052907
.NET 7 中的新功能
在這篇博文中,我們將重點介紹 .NET 團隊專注于交付的主要主題:
統一:一個 BCL、新的 TFM、對 ARM64 的本機支持、Linux 上增強的 .NET 支持
現代:持續的性能改進、開發人員生產力增強,例如容器優先的工作流程、從相同的代碼庫構建跨平臺的移動和桌面應用程序
.NET 適用于云原生應用:易于構建和部署分布式云原生應用
簡單:使用 C# 11 簡化和編寫更少的代碼、針對云原生應用程序的 HTTP/3 和最小 API 改進
性能:多項性能改進
下面,我們將更詳細地介紹這些主題,并分享更多關于這項工作為何如此重要的背景信息。
場景
.NET 7 用途廣泛,您可以在任何平臺上構建任何應用程序。
讓我們重點介紹從今天開始可以使用 .NET 實現的一些場景:
從在瀏覽器中運行的 React 代碼調用現有.NET庫,通過包含經過優化以在 WebAssembly 上運行的流線型 .NET運行時。
使用強類型 C#訪問存儲在 SQL Server 數據庫中的 JSON 文檔的內容。
只需編寫幾行代碼,即可快速構建和部署使用 OpenAPI 自動記錄的安全 REST 端點。
使用 Ahead of Time (AOT) 編譯從 C# 源代碼生成簡化的本機應用程序,并直接發布到容器映像。
運行一個 .NET Core 應用程序,該應用程序使用內置 API 將內容壓縮并存檔到 Linux 友好的文件gz中。
使用為每個目標平臺創建本機代碼和組件的單一代碼庫和設計,實現您對 Android、iOS 和 Windows 上的移動應用程序的愿景。
通過使用升級助手自動遷移舊版應用程序并在 CoreWCF 的幫助下現代化您的 Windows Communication Foundation (WCF) Web 服務,獲得.NET 7 的性能優勢。
使用反映您的架構和設計選擇的樣板模板,讓開發人員比以往任何時候都更容易啟動新應用程序。
使用ReadKey 在 Unix/Linux 中更好地處理組合鍵和修飾鍵。
統一
▌一個基類庫 (BCL)
.NET 7 版本是我們 .NET 統一之旅中的第三個主要版本(自 2016 年 .NET 5 以來)。
使用 .NET 7,您只需學習一次,就可以通過一個 SDK、一個運行時、一組基礎庫重復使用您的技能來構建多種類型的應用程序(云、Web、桌面、移動、游戲、IoT 和 AI)。
▌面向 .NET 7
當您以應用程序或庫中的框架為目標時,您正在指定要提供的 API 集。要以 .NET 7 為目標,只需更改項目中的目標框架即可。
<TargetFramework>net7.0</TargetFramework>
針對 net7.0 Target Framework Moniker (TFM) 的應用程序將在所有受支持的操作系統和 CPU 架構上運行。它們使您可以訪問 .NET 7 中的所有 API 以及一堆特定于操作系統的 API,例如:
net7.0-android
net7.0-ios
net7.0-maccatalyst
net7.0-macos
net7.0-tvos
net7.0-windows
通過 net7.0 TFM 公開的 API 旨在隨時隨地工作。如果您懷疑 .NET 7 是否支持 API,您可以隨時查看。這是一個新添加的接口 IJsonTypeInfoResolver 的示例,您可以看到它現在已內置到 .NET 7 中:
隨時查看
https://apisof.net/
▌ARM64
隨著行業向 ARM 發展,.NET 也是如此。ARM CPU 的最大優勢之一是電源效率。這以最低的功耗帶來最高的性能。換句話說,您可以事半功倍。在 .NET 5 中,我們描述了我們針對 ARM64 所做的性能計劃。現在,在兩個版本之后,我們想與您分享我們已經走了多遠。我們的持續目標是將 x64 的性能與 ARM64 相匹配,以幫助我們的客戶將他們的 .NET 應用程序遷移到 ARM。
▌運行時改進
我們在調查 x64 和 ARM64 時遇到的一個挑戰是發現無法從 ARM64 機器正確讀取 L3 緩存大小。當無法從操作系統或機器的 BIOS 中獲取 L3 緩存大小,我們通過更改啟發式方法以返回近似大小。現在我們可以更好地估計每個 L3 緩存大小的內核數。
Core count | L3 cache size |
1~4 | 4MB |
5~16 | 8MB |
17~64 | 16MB |
65+ | 32MB |
接下來是我們對 LSE 原子的理解。如果您不熟悉,它提供了原子 API 來獲得對關鍵區域的獨占訪問權限。在 CISC 架構 x86-x64 機器中,內存上的讀-修改-寫 (RMW) 操作可以通過添加鎖定前綴的單個指令執行。
但是在 RISC 架構的機器上,RMW 操作是不允許的,所有的操作都是通過寄存器來完成的。因此,對于并發場景,它們有一對指令。“Load Acquire” (ldaxr) 獲得對內存區域的獨占訪問權限,因此其他內核無法訪問它,而“Store Release” (stlxr) 則釋放對其他內核的訪問權限。在這些對之間,執行關鍵操作。如果在使用 ldaxr 加載內容后,由于其他 CPU 在內存上操作而導致 stlxr 操作失敗,則可以通過這個代碼重試(cbnz jumps back to retry)該操作。
ARM 在 v8.1 中引入了 LSE 原子指令。有了這些指令,這些操作可以比傳統版本用更少的代碼和更快的速度完成。當我們為 Linux 啟用此功能并隨后將其擴展到 Windows 時,我們看到了大約 45% 的性能提升。
▌庫改進
為了優化使用內在函數的庫,我們添加了新的跨平臺助手。其中包括Vector64、Vector128和Vector256 的助手。跨平臺助手允許通過用與硬件無關的內在函數替換特定于硬件的內在函數來統一矢量化算法。這將使任何平臺上的用戶受益,但我們預計 ARM64 將受益最大,因為沒有 ARM64 專業知識的開發人員仍然能夠使用幫助程序來利用 Arm64 硬件內在函數。
將諸如 EncodeToUtf8 和 DecodeFromUtf8 等 API 從 SSE3 實現重寫為基于 Vector 的 API 可以提供高達 60% 的改進。
轉換 NarrowUtf16ToAscii() 和 GetIndexOfFirstNonAsciiChar() 等其他 API 可以實現高達 35% 的性能提升。
▌性能影響
通過我們在 .NET 7 中的工作,許多 MicroBenchmark 提高了 10-60%。當我們開始 .NET 7 時,ARM64 的每秒請求數 (RPS) 較低,但慢慢克服了 x64 的奇偶校驗。
同樣對于延遲(以毫秒為單位),我們將橋接 x64 的奇偶校驗。
有關更多詳細信息,請查看 .NET 7 中的 ARM64 性能改進。
.NET 7 中的 ARM64?性能改進
https://devblogs.microsoft.com/dotnet/arm64-performance-improvements-in-dotnet-7/?ocid=AID3052907
▌Linux 上增強的 .NET 支持
.NET 6 包含在 Ubuntu 22.04 (Jammy) 中,可以使用 apt install dotnet6 命令安裝。此外,還有一個優化的、預構建的、開箱即用的超小型容器鏡像。
dotnetapp % docker run --rm dotnetapp-chiseled4242 ,d ,d42 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 7.0.0-preview.7.22375.6
Linux 5.10.104-linuxkit #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022OSArchitecture: Arm64
ProcessorCount: 4
TotalAvailableMemoryBytes:?3.83?GiB
▌64 位 IBM Power 支持
除了 x64 架構(64 位 Intel/AMD)、ARM64(64 位 ARM)和 s390x(64 位 IBM Z)之外,.NET 現在也可用于針對 RHEL 的 ppc64le(64 位 IBM Power)架構8.7 和 RHEL 9.1。
超過 25,000 名 IBM Power 客戶可以在 Windows x86 上整合現有的 .NET 應用程序,以便在與其 IBM i 和 AIX 業務應用程序和數據庫相同的 Power 平臺上運行。這樣做極大地提高了可持續性,將碳足跡減少了多達 5 倍,并結合了 RHEL 和 OpenShift 容量的本地即用即付擴展,同時提供行業領先的端到端企業事務和數據安全性。
現代
.NET 7 專為現代云原生應用程序、移動客戶端、邊緣服務和桌面技術而構建。使用 .NET MAUI 在不影響本機性能的情況下,使用單個代碼庫創建移動體驗。使用 C# 和 Razor 模板等熟悉的技術構建響應式單頁應用程序 (SPA),這些應用程序在瀏覽器中運行并作為漸進式 Web 應用程序 (PWA) 脫機運行。這些更快、更現代的體驗不僅僅適用于新應用。.NET 升級助手將提供有關兼容性的反饋,并在某些情況下將您的應用程序完全遷移到 .NET 6 和 .NET 7。
▌.NET MAUI
NET MAUI 現在是 .NET 7 的一部分,具有大量改進和新功能。通過閱讀最新的.NET MAUI 博客公告,您可以了解 .NET MAUI 以及它使您能夠為所有移動設備構建應用程序的方式。
最新的 .NET MAUI 博客公告
https://devblogs.microsoft.com/dotnet/dotnet-maui-dotnet-7?ocid=AID3052907
▌Blazor
Blazor 不斷發展,.NET 7 包括許多重大改進。Blazor 現在支持處理位置更改事件,改進了 WebAssembly 調試體驗,以及對使用 OpenID Connect 進行身份驗證的開箱即用支持。要了解更多信息,請閱讀最新的 Blazor 團隊博客文章。
最新的 Blazor 團隊博客文章:
https://devblogs.microsoft.com/dotnet/category/blazor/?ocid=AID3052907
▌升級助手
.NET 升級助手提供分步指導、見解和自動化,將您的舊應用程序遷移到 .NET 6 和 .NET 7。在某些情況下,它可以為您執行遷移!在對舊代碼庫進行現代化改造時,它有助于減少時間和復雜性。例如,了解如何借助 CoreWCF 將 WCF 應用程序引入 .NET Core。使用 .NET 7,改進的體驗包括:
NET 到 ASP.NET Core
Web 適配器(預覽版)
增量遷移(預覽)
為 WinForms、WPF 和控制臺/類庫添加了更多分析器和代碼修復程序
分析二進制文件的能力
UWP 到 Windows Appp SDK 和 WinUI 支持
準備好將您的應用程序遷移到迄今為止最新且性能最快的 .NET 上了嗎?請立即下載升級助手!
下載升級助手
https://dotnet.microsoft.com/platform/upgrade-assistant
.NET 適用于云原生應用
.NET 7 使開箱即用地構建云原生應用程序變得前所未有的容易。使用 Visual Studio 的連接服務安全地連接到數據服務并安全地加密用戶機密文件或 Azure Key Vault 中的連接字符串。將您的應用程序直接構建到容器映像中。使用 Entity Framework 7 編寫強類型語言集成查詢 (LINQ) 查詢,這些查詢使用 SQL Server 的 JSON 支持從存儲在關系數據庫中的 JSON 文檔中快速提取內容。只需幾行代碼,即可通過經過身份驗證的端點交付安全的 JSON 文檔,并提供最少的 API 體驗。使用 Open Telemetry 收集有關您正在運行的應用程序的見解。
▌Azure 支持
.NET 7 不僅非常適合構建云原生應用程序;Azure 的 PaaS 服務,例如適用于 Windows 和 Linux 的應用服務、Static Web 應用、Azure Functions 和 Azure 容器應用,已經都支持 .NET 7 了 就像之前的? .NET 5.0 和 6.0一樣。在發布的第一周,您可能會遇到 .NET 7 應用程序的啟動時間稍長一些,因為 .NET 7 SDK 將及時安裝,以便客戶使用 .NET 7 創建新的應用程序服務。此外,如果您正在運行 .NET 7 預覽版,只需重新啟動應用服務即可將您更新到 GA。
▌內置容器支持
容器的普及和實際使用正在上升,對于許多公司來說,它們代表了部署到云的首選方式。但是,使用容器會為團隊的積壓工作增加新的工作,包括構建和發布鏡像、檢查安全性和合規性以及優化鏡像的性能。我們相信有機會使用 .NET 容器創建更好、更簡化的體驗。
現在,您只需使用 dotnet publish 即可創建應用程序的容器化版本。我們構建此解決方案的目標是與現有構建邏輯無縫集成,利用我們自己豐富的 C# 工具和運行時性能,并直接內置到 .NET SDK 的盒子中以進行定期更新。
容器圖像現在是 .NET SDK 支持的輸出類型:
# create a new project and move to its directory
dotnet new mvc -n my-awesome-container-app
cd my-awesome-container-app
# add a reference to a (temporary) package that creates the container
dotnet add package Microsoft.NET.Build.Containers
# publish your project for linux-x64
dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer
要了解有關內置容器支持的更多信息,請參閱官宣對 .NET SDK 的內置容器支持。
官宣對 .NET SDK 的內置容器支持
https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/?ocid=AID3052907
▌Microsoft Orleans
Microsoft Orleans 7.0 將使用“plain old CLR object”(POCO) Grains 提供更簡單的編程模型,提供比 3.x 高 150% 的性能,并引入新的序列化和不變性改進。ASP.NET Core 開發人員可以使用 Orleans 簡單地添加分布式狀態,并確信他們的應用程序將在不增加復雜性的情況下水平擴展。我們將繼續投資以使 Orleans 功能更接近 ASP.NET 堆棧,以確保您的 Web 和 API 應用程序為云規模、分布式托管方案甚至多云部署做好準備。Orleans 支持大多數流行的存儲機制和數據庫,并且能夠在 ASP.NET Core 可以運行的任何地方運行,Orleans 是讓您的 .NET 應用程序具有云原生分布式功能的絕佳選擇,而無需學習新的框架或工具集。了解有關 Orleans 7 的更多信息。
了解有關 Orleans 7 的更多信息
https://devblogs.microsoft.com/dotnet/whats-new-in-orleans-7/?ocid=AID3052907
▌可觀察性
可觀察性的目標是幫助您更好地了解應用程序在擴展和技術復雜性增加時的狀態。.NET 接受了 OpenTelemetry,同時還在 .NET 7 中進行了以下改進。
▌介紹 Activity.Current 更改事件
分布式跟蹤的典型實現使用 AsyncLocal<T> 來跟蹤托管線程的“跨度上下文”。通過使用采用 valueChangedHandler 參數的 AsyncLocal<T> 構造函數來跟蹤跨度上下文的更改。但是,隨著 Activity 成為代表spans 的標準被OpenTelemetry 所使用,因此不可能設置值更改處理程序,但是 上下文可以通過 Activity.Current 來跟蹤。可以使用新的更改事件來接收所需的通知。
Activity.CurrentChanged += CurrentChanged;
void CurrentChanged(object? sender, ActivityChangedEventArgs e)
{Console.WriteLine($"Activity.Current value changed from Activity: {e.Previous.OperationName} to Activity: {e.Current.OperationName}");
}
▌公開高性能活動屬性枚舉器方法
以下新公開的方法可以在性能關鍵場景中用于枚舉 Activity 的標簽、鏈接和事件屬性,而無需任何額外的分配和高性能項訪問:
Activity a = new Activity("Root");
a.SetTag("key1", "value1");
a.SetTag("key2", "value2");
foreach (ref readonly KeyValuePair<string, object?> tag in a.EnumerateTagObjects())
{Console.WriteLine($"{tag.Key}, {tag. Value}");
▌公開高性能 ActivityEvent 和 ActivityLink 標簽枚舉器方法
與上述類似,ActivityEvent 和 ActivityLink Tag 對象也被公開,以減少對高性能項目訪問的任何額外分配:
var tags = new List<KeyValuePair<string, object?>>()
{new KeyValuePair<string, object?>("tag1", "value1"),new KeyValuePair<string, object?>("tag2", "value2"),
};
ActivityLink link = new ActivityLink(default, new ActivityTagsCollection(tags));
foreach (ref readonly KeyValuePair<string, object?> tag in link.EnumerateTagObjects())
{
// Consume the link tags without any extra allocations or value copying.
}
ActivityEvent e = new ActivityEvent("SomeEvent", tags: new ActivityTagsCollection(tags));
foreach (ref readonly KeyValuePair<string, object?> tag in e.EnumerateTagObjects())
{
// Consume the event's tags without any extra allocations or value copying.
}
簡單
▌C# 11 & F# 7
C# 和 F# 語言的最新成員是 C# 11 和 F# 7。C# 11 使通用數學等新功能成為可能,同時通過對象初始化改進、原始字符串文字等簡化了代碼。
▌通用數學
.NET 7 為基類庫引入了新的數學相關通用接口。這些接口的可用性意味著您可以將泛型類型或方法的類型參數約束為“類似數字”。此外,C# 11 及更高版本允許您定義靜態虛擬接口成員。因為運算符必須聲明為靜態,所以這個新的 C# 功能允許在新接口中為類似數字的類型聲明運算符。
總之,這些創新讓您可以通用地執行數學運算——也就是說,無需知道您正在使用的確切類型。例如,如果您想編寫一個將兩個數字相加的方法,之前您必須為每種類型添加方法的重載(例如,static int Add(int first, int second) 和 static float Add(float first, float second). 現在您可以編寫一個單一的泛型方法,其中類型參數被限制為類似數字的類型。
static T Add<T>(T left, T right) where T : INumber<T>
{return left + right;
}
在此方法中,類型參數 T 被約束為實現新 INumber<TSelf> 接口的類型。INumber<TSelf> 實現了 IAdditionOperators<TSelf,TOther,TResult> 接口,其中包含 + 運算符。這通常允許該方法添加兩個數字。此方法可用于 .NET 的任何內置數字類型,因為它們都已更新為在 .NET 7 中實現 INumber<TSelf>。
庫作者將從通用數學接口中受益最多,因為他們可以通過刪除“冗余”重載來簡化他們的代碼庫。其他開發人員將間接受益,因為他們使用的 API 可能會開始支持更多類型。
▌原始字符串文字
現在有一種新的字符串文字格式。原始字符串文字可以包含任意文本,包括空格、換行符、嵌入引號和其他特殊字符,而無需轉義序列。原始字符串文字至少以三個雙引號 (""") 字符開頭,并以相同數量的雙引號字符結尾。
string longMessage = """This is a long message.It has several lines.Some are indentedmore than others.Some should start at the first column.Some have "quoted text" in them.""";
▌.NET 庫
許多 .NET 的第一方庫在 .NET 7 版本中得到了顯著改進。您將看到對 Microsoft.Extensions.* 包的可為空注釋的支持、System.Text.Json 的合同自定義和類型層次結構,以及幫助您以磁帶存檔 (TAR) 格式編寫數據的新 Tar API 等等。
▌Microsoft.Extensions 的可空注釋
所有 Microsoft.Extensions.* 庫現在都包含 C# 8 選擇加入功能,該功能允許編譯器跟蹤引用類型可空性以捕獲潛在的空取消引用。這有助于您將代碼導致運行時拋出 System.NullReferenceException 的可能性降至最低。
▌System.Composition.Hosting
添加了一個新 API 以允許 System.Composition.Hosting 容器中的單個對象實例通過 API ComposeExportedValue(CompositionContainer, T) 提供與 System.ComponentModel.Composition.Hosting 的舊接口類似的功能。
namespace System.Composition.Hosting
{public class ContainerConfiguration{public ContainerConfiguration WithExport<TExport>(TExport exportedInstance);public ContainerConfiguration WithExport<TExport>(TExport exportedInstance, string contractName = null, IDictionary<string, object> metadata = null);public ContainerConfiguration WithExport(Type contractType, object exportedInstance);public ContainerConfiguration WithExport(Type contractType, object exportedInstance, string contractName = null, IDictionary<string, object> metadata = null);}
}
▌將微秒和納秒添加到 TimeStamp、DateTime、DateTimeOffset 和 TimeOnly
在 .NET 7 之前,各種日期和時間結構中可用的最小時間增量是 Ticks 屬性中的“tick”。作為參考,一個tick是 100ns。傳統上,開發人員必須對“tick”值執行計算以確定微秒和納秒值。在 .NET 7 中,我們在日期和時間實現中引入了微秒和納秒:
namespace System
{public struct DateTime{public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond);public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.DateTimeKind kind);public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.Globalization.Calendar calendar);public int Microsecond { get; }public int Nanosecond { get; }public DateTime AddMicroseconds(double value);}public struct DateTimeOffset{public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.TimeSpan offset);public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.TimeSpan offset, System.Globalization.Calendar calendar);public int Microsecond { get; }public int Nanosecond { get; }public DateTimeOffset AddMicroseconds(double microseconds);}public struct TimeSpan{public const long TicksPerMicrosecond = 10L;public const long NanosecondsPerTick = 100L;public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds, int microseconds);public int Microseconds { get; }public int Nanoseconds { get; }public double TotalMicroseconds { get; }public double TotalNanoseconds { get; }public static TimeSpan FromMicroseconds(double microseconds);}public struct TimeOnly{public TimeOnly(int hour, int minute, int second, int millisecond, int microsecond);public int Microsecond { get; }public int Nanosecond { get; }}
}
Microsoft.Extensions.Caching
We added metrics support for IMemoryCache, which is a new API of MemoryCacheStatistics that holds cache hits, misses, and estimated size for IMemoryCache. You can get an instance of MemoryCacheStatistics by calling GetCurrentStatistics() when the flag TrackStatistics is enabled.
The GetCurrentStatistics() API allows app developers to use event counters or metrics APIs to track statistics for one or more memory cache.
// when using `services.AddMemoryCache(options => options.TrackStatistics = true);` to instantiate
[EventSource(Name = "Microsoft-Extensions-Caching-Memory")]
internal sealed class CachingEventSource : EventSource
{public CachingEventSource(IMemoryCache memoryCache){_memoryCache = memoryCache;}protected override void OnEventCommand(EventCommandEventArgs command){if (command.Command == EventCommand.Enable){if (_cacheHitsCounter == null){_cacheHitsCounter = new PollingCounter("cache-hits", this, () => _memoryCache.GetCurrentStatistics().CacheHits){DisplayName = "Cache hits",};}}}
}
▌Microsoft.Extensions.Caching
我們添加了對 IMemoryCache 的指標支持,這是 MemoryCacheStatistics 的一個新 API,用于保存 IMemoryCache 的緩存命中、未命中和估計大小。當啟用標志 TrackStatistics 時,您可以通過調用 GetCurrentStatistics() 來獲取 MemoryCacheStatistics 的實例。
GetCurrentStatistics() API 允許應用程序開發人員使用事件計數器或指標 API 來跟蹤一個或多個內存緩存的統計信息。
// when using `services.AddMemoryCache(options => options.TrackStatistics = true);` to instantiate
[EventSource(Name = "Microsoft-Extensions-Caching-Memory")]
internal sealed class CachingEventSource : EventSource
{public CachingEventSource(IMemoryCache memoryCache){_memoryCache = memoryCache;}protected override void OnEventCommand(EventCommandEventArgs command){if (command.Command == EventCommand.Enable){if (_cacheHitsCounter == null){_cacheHitsCounter = new PollingCounter("cache-hits", this, () => _memoryCache.GetCurrentStatistics().CacheHits){DisplayName = "Cache hits",};}}}
}
然后,您可以使用 dotnet-counters 工具查看以下統計信息:
Press p to pause, r to resume, q to quit.Status: Running
[System.Runtime]CPU Usage (%) 0Working Set (MB) 28
[Microsoft-Extensions-Caching-MemoryCache]cache-hits 269
▌System.Formats.Tar APIs
我們添加了一個新的 System.Formats.Tar 程序集,其中包含允許讀取、寫入、歸檔和提取 Tar 檔案的跨平臺 API。SDK 甚至使用這些 API 來創建容器作為發布目標。
// Generates a tar archive where all the entry names are prefixed by the root directory 'SourceDirectory'
TarFile.CreateFromDirectory(sourceDirectoryName: "/home/dotnet/SourceDirectory/", destinationFileName: "/home/dotnet/destination.tar", includeBaseDirectory: true);
// Extracts the contents of a tar archive into the specified directory, but avoids overwriting anything found inside
TarFile.ExtractToDirectory(sourceFileName: "/home/dotnet/destination.tar", destinationDirectoryName: "/home/dotnet/DestinationDirectory/", overwriteFiles: false);
▌類型轉換器
現在有針對新添加的原始類型 DateOnly、TimeOnly、Int128、UInt128 和 Half 的公開類型轉換器。
namespace System.ComponentModel
{public class DateOnlyConverter : System.ComponentModel.TypeConverter{public DateOnlyConverter() { }}public class TimeOnlyConverter : System.ComponentModel.TypeConverter{public TimeOnlyConverter() { }}public class Int128Converter : System.ComponentModel.BaseNumberConverter{public Int128Converter() { }}public class UInt128Converter : System.ComponentModel.BaseNumberConverter{public UInt128Converter() { }}public class HalfConverter : System.ComponentModel.BaseNumberConverter{public HalfConverter() { }}
}
這些是有用的轉換器,可以輕松轉換為更原始的類型。
TypeConverter dateOnlyConverter = TypeDescriptor.GetConverter(typeof(DateOnly));
// produce DateOnly value of DateOnly(1940, 10, 9)
DateOnly? date = dateOnlyConverter.ConvertFromString("1940-10-09") as DateOnly?;
TypeConverter timeOnlyConverter = TypeDescriptor.GetConverter(typeof(TimeOnly));
// produce TimeOnly value of TimeOnly(20, 30, 50)
TimeOnly? time = timeOnlyConverter.ConvertFromString("20:30:50") as TimeOnly?;
TypeConverter halfConverter = TypeDescriptor.GetConverter(typeof(Half));
// produce Half value of -1.2
Half? half = halfConverter.ConvertFromString(((Half)(-1.2)).ToString()) as Half?;
TypeConverter Int128Converter = TypeDescriptor.GetConverter(typeof(Int128));
// produce Int128 value of Int128.MaxValue which equal 170141183460469231731687303715884105727
Int128? int128 = Int128Converter.ConvertFromString("170141183460469231731687303715884105727") as Int128?;
TypeConverter UInt128Converter = TypeDescriptor.GetConverter(typeof(UInt128));
// produce UInt128 value of UInt128.MaxValue Which equal 340282366920938463463374607431768211455
UInt128? uint128 = UInt128Converter.ConvertFromString("340282366920938463463374607431768211455") as UInt128?;
▌System.Text.Json 合約定制
System.Text.Json 通過為該類型構造 JSON 契約來確定給定 .NET 類型如何被序列化和反序列化。契約派生自類型的形狀——例如其可用的構造函數、屬性和字段,以及它是否實現 IEnumerable 或 IDictionary——在運行時使用反射或在編譯時使用源生成器。在以前的版本中,假設用戶能夠修改類型聲明,他們可以使用 System.Text.Json 屬性注釋對派生合約進行有限的調整。
給定類型 T 的合約元數據使用 JsonTypeInfo<T> 表示,在以前的版本中,它用作源生成器 API 中專用的不透明令牌。從 .NET 7 開始,JsonTypeInfo 合同元數據的大多數方面都已公開并可供用戶修改。合約定制允許用戶使用 IJsonTypeInfoResolver 接口的實現編寫自己的 JSON 合約解析邏輯:
public interface IJsonTypeInfoResolver
{JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options);
}
合同解析器為給定的 Type 和 JsonSerializerOptions 組合返回配置的 JsonTypeInfo 實例。如果解析器不支持指定輸入類型的元數據,它可以返回 null。
默認的基于反射的序列化程序執行的合同解析現在通過實現 IJsonTypeInfoResolver 的 DefaultJsonTypeInfoResolver 類公開。此類允許用戶通過自定義修改擴展默認的基于反射的解析,或將其與其他解析器(例如源生成的解析器)結合使用。
從 .NET 7 開始,源生成中使用的 JsonSerializerContext 類也實現了 IJsonTypeInfoResolver。要了解有關源生成器的更多信息,請參閱如何在 System.Text.Json 中使用源生成。
可以使用新的 TypeInfoResolver 屬性為 JsonSerializerOptions 實例配置自定義解析器:
// configure to use reflection contracts
var reflectionOptions = new JsonSerializerOptions
{TypeInfoResolver = new DefaultJsonTypeInfoResolver()
};
// configure to use source generated contracts
var sourceGenOptions = new JsonSerializerOptions
{TypeInfoResolver = EntryContext.Default
};
[JsonSerializable(typeof(MyPoco))]
public partial class EntryContext : JsonSerializerContext { }
▌System.Text.Json 類型層次結構
System.Text.Json 現在支持用戶定義類型層次結構的多態序列化和反序列化。這可以通過使用新的 JsonDerivedTypeAttribute 裝飾類型層次結構的基類來啟用:
[JsonDerivedType(typeof(Derived))]
public class Base
{public int X { get; set; }
}
public class Derived : Base
{public int Y { get; set; }
}
此配置為 Base 啟用多態序列化,特別是在運行時類型為 Derived 時:
Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "X" : 0, "Y" : 0 }
請注意,這不會啟用多態反序列化,因為有效負載將作為 Base 往返:
Base value = JsonSerializer.Deserialize<Base>(@"{ ""X"" : 0, ""Y"" : 0 }");
value is Derived; // false
查看 .NET 7 中 System.Text.Json 的新增功能博客文章,了解有關類型層次結構的更多詳細信息。
.NET 7 中 System.Text.Json 的新增功能博客文章
https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-7/#type-hierarchies?ocid=AID3052907
▌.NET SDK
.NET SDK 繼續添加新功能,讓您比以往任何時候都更有效率。在 .NET 7 中,我們改進了您使用 .NET CLI、創作模板和在中央位置管理包的體驗。
▌CLI 解析器和選項卡完成
dotnet new 命令為用戶熟悉和喜愛的許多子命令提供了更加一致和直觀的界面。還支持模板選項和參數的制表符完成。CLI 現在會在用戶鍵入時提供有關有效參數和選項的反饋。
以下是新的幫助輸出示例:
? dotnet new --help
Description:Template Instantiation Commands for .NET CLI.
Usage:dotnet new [<template-short-name> [<template-args>...]] [options]dotnet new [command] [options]
Arguments:<template-short-name> A short name of the template to create.<template-args> Template specific options to use.
Options:-?, -h, --help Show command line help.
Commands:install <package> Installs a template package.uninstall <package> Uninstalls a template package.update Checks the currently installed template packages for update, and install the updates.search <template-name> Searches for the templates on NuGet.org.list <template-name> Lists templates containing the specified template name. If no name is specified, lists all templates.
一直以來,dotnet CLI 都支持 tab 補全,其中包括 PowerShell、bash、zsh 和 fish 等流行的 shell。然而,實現有意義的補全取決于單獨的 dotnet 命令。對于 .NET 7,dotnet new 命令學習了如何提供制表符補全。
? dotnet new angular
angular grpc razor viewstart worker -h
blazorserver mstest razorclasslib web wpf /?
blazorwasm mvc razorcomponent webapi wpfcustomcontrollib /h
classlib nugetconfig react webapp wpflib install
console nunit reactredux webconfig wpfusercontrollib list
editorconfig nunit-test sln winforms xunit search
gitignore page tool-manifest winformscontrollib --help uninstall
globaljson proto viewimports winformslib -? update
這有助于您在創建新的 .NET 項目時做出選擇,以了解哪些選項和參數可供您使用。
? dotnet new web --dry-run
--dry-run --language --output -lang
--exclude-launch-settings --name --type -n
--force --no-https -? -o
--framework --no-restore -f /?
--help --no-update-check -h /h
此外,給定命令通常錯誤或不支持哪些常見選項和參數。相反,您只會看到當前版本的 .NET CLI 支持的內容。
? dotnet new blazorserver --auth Individual
Individual IndividualB2C MultiOrg None SingleOrg Windows
▌模板創作
.NET 7 將約束的概念添加到 .NET 模板。約束允許您定義允許模板的上下文,這有助于模板引擎確定應在 dotnet new list 等命令中顯示哪些模板。在此版本中,我們添加了對三種類型約束的支持:
操作系統:根據用戶的操作系統限制模板
模板引擎主機:根據執行模板引擎的主機來限制模板。這通常是 .NET CLI 本身,或者是嵌入式場景,如 Visual Studio 或 Visual Studio for Mac 中的“新建項目”對話框。
已安裝的工作負載:要求在模板可用之前安裝指定的 .NET SDK 工作負載。
在所有情況下,描述這些約束就像在模板的配置文件中添加一個新的約束部分一樣簡單:
"constraints": {"web-assembly": {"type": "workload","args": "wasm-tools"},}
我們還添加了選擇參數的新功能。這是用戶在單個選擇中指定多個值的能力。這可以以與使用標志樣式枚舉相同的方式使用。此類參數的常見示例可能是:
在 Web 模板上選擇多種形式的身份驗證。
在 MAUI 模板中一次選擇多個目標平臺(iOS、Android、Web)。
選擇加入此行為就像在模板配置中的參數定義中添加 "allowMultipleValues": true 一樣簡單。完成后,您將可以訪問多個幫助函數以在模板內容中使用,并幫助檢測用戶選擇的特定值。
▌中央包管理
依賴管理是 NuGet 的核心功能。管理單個項目的依賴關系很容易。管理多項目解決方案的依賴關系可能會變得很困難,因為它們的規模和復雜性開始擴大。在您管理許多不同項目的公共依賴項的情況下,您可以利用 NuGet 的中央包管理功能從一個位置輕松完成所有這些工作。
要開始集中包管理,您可以在解決方案的根目錄中創建一個 Directory.Packages.props 文件,并將 MSBuild 屬性 ManagePackageVersionsCentrally 設置為 true。
在內部,您可以使用定義包 ID 和版本的 <PackageVersion /> 元素定義解決方案所需的各個包版本。
<Project><PropertyGroup><ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally></PropertyGroup><ItemGroup><PackageVersion Include="Newtonsoft.Json" Version="13.0.1" /></ItemGroup>
</Project>
在解決方案的項目中,您可以使用您熟悉和喜愛的相應 <PackageReference /> 語法,但沒有 Version 屬性來推斷集中管理的版本。
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net6.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Newtonsoft.Json" /></ItemGroup>
</Project>
性能
性能一直是每個 .NET 版本的重要組成部分。每年,.NET 團隊都會發布一篇關于最新改進的博客。以下是最新性能帖子的簡要概述:
TL;DR:.NET 7 速度很快。在這個版本中,一千多個影響性能的 PR 進入了運行時和核心庫,更不用說 ASP.NET Core 和 Windows Forms 和 Entity Framework 及其他方面的所有改進。這是有史以來最快的 .NET。如果你的經理問你為什么你的項目應該升級到 .NET 7,你可以說“除了發行版中的所有新功能之外,.NET7超級快”。– Stephen Toub
▌On Stack Replacement (OSR)
堆棧上替換 (OSR) 允許運行時在方法執行過程中更改當前正在運行的方法執行的代碼,盡管這些方法在“堆棧上”處于活動狀態。它作為分層編譯的補充。
OSR 允許長時間運行的方法在執行過程中切換到更優化的版本,因此運行時可以首先快速 JIT 所有方法,然后在通過分層編譯頻繁調用這些方法或通過長時間運行循環時過渡到更優化的版本 操作系統。
OSR 提高了啟動時間。幾乎所有的方法現在最初都是由快速 JIT 進行的。我們已經看到像 Avalonia “IL” spy 這樣的 jitting-heavy 應用程序的啟動時間縮短了 25%,我們跟蹤的各種 TechEmpower 基準測試顯示,首次請求的時間縮短了 10-30%。
OSR 還可以提高應用程序的性能,尤其是使用動態 PGO 的應用程序,因為現在可以更好地優化帶有循環的方法。例如,啟用 OSR 后,Array2 微基準測試顯示出顯著的改進。
▌Profile-Guided Optimization (PGO)
配置文件引導優化(PGO) 在許多語言和編譯器中已經存在了很長時間。基本思想是編譯應用程序,要求編譯器將檢測注入應用程序以跟蹤各種有趣的信息。然后,你讓你的應用程序通過它的步伐,運行各種常見的場景,使該儀器“分析”應用程序執行時發生的情況,然后保存結果。然后重新編譯應用程序,將這些檢測結果反饋給編譯器,并允許它優化應用程序以準確地使用它。
這種 PGO 方法被稱為“靜態 PGO”,因為所有信息都是在實際部署之前收集的,而這也是 .NET 多年來以各種形式所做的事情。.NET 中有趣的發展是“動態 PGO”,它是在 .NET 6 中引入的,但默認情況下是關閉的。
動態 PGO 利用分層編譯。JIT 檢測第 0 層代碼以跟蹤方法被調用的次數,或者在循環的情況下,循環執行了多少次。分層編譯可以提供多種可能性。例如,它可以準確地跟蹤哪些具體類型被用作接口調度的目標,然后在第 1 層中,專門化代碼以期望最常見的類型(這被稱為“受保護的去虛擬化”或 GDV )。你可以在這個小例子中看到這一點。將 DOTNET_TieredPGO 環境變量設置為 1,然后在 .NET 7 上運行它:
class Program
{static void Main(){IPrinter printer = new Printer();for (int i = 0; ; i++){DoWork(printer, i);}}static void DoWork(IPrinter printer, int i){printer.PrintIfTrue(I == int.MaxValue);}interface iPrinter{void PrintIfTrue(bool condition);}class Printer : iPrinter{public void PrintIfTrue(bool condition){if (condition) Console.WriteLine“"Print”");}}
}
使用 PGO 獲得的主要改進是它現在可以與 .NET 7 中的 OSR 一起使用。這意味著執行接口調度的熱運行方法可以獲得這些去虛擬化/內聯優化。
禁用 PGO 后,您可以獲得相同的 .NET 6 和 .NET 7 性能吞吐量。
方法 | 運行時 | 均值 | 比率 |
DelegatePGO | .NET 6.0 | 1.665?ns | 1.00 |
DelegatePGO | .NET 7.0 | 1.659 ns | 1.00 |
但是,當您通過 <TieredPGO>true</TieredPGO> 或 DOTNET_TieredPGO=1 的環境變量在 .csproj 中啟用動態 PGO 時,情況會發生變化。.NET 6 的速度提高了約 14%,但 .NET 7 的速度提高了約 3 倍。
方法 | 運行時 | 均值 | 比率 |
DelegatePGO | .NET 6.0 | 1.427.7 ns | 1.00 |
DelegatePGO | .NET 7.0 | 539.0 | 0.38 |
▌Native AOT
對于許多人來說,軟件上下文中的“性能”一詞與吞吐量有關。它可以執行多快?它每秒可以處理多少數據?它每秒可以處理多少個請求?還有很多。但性能還有很多其他方面。它消耗多少內存?它以多快的速度啟動并開始做一些有用的事情?它在磁盤上消耗多少空間?下載需要多長時間?
然后是相關的擔憂。實現這些目標需要哪些依賴項?它需要執行哪些類型的操作來實現這些目標,以及所有這些操作在目標環境中是否允許?如果這一段中的任何一段引起了您的共鳴,那么您就是現在在 .NET 7 中提供的本機 AOT 支持的目標受眾。
.NET 長期以來一直支持 AOT 代碼生成。例如,.NET Framework 有 ngen 的形式,.NET Core 有 crossgen 的形式。這兩種解決方案都涉及一個標準的 .NET 可執行文件,其中一些 IL 已經編譯為匯編代碼,但并非所有方法都會為它們生成匯編代碼,各種事情都可能使生成的匯編代碼無效,外部 .NET 程序集沒有任何本機匯編代碼 加載等等,在所有這些情況下,運行時還是繼續使用 JIT 編譯器。而原生 AOT 不同,它是 CoreRT 的演變,CoreRT 本身是 .NET Native 的演變,完全脫離于 JIT。
發布構建生成的二進制文件是目標平臺特定于平臺的文件格式(例如,Windows 上的 COFF、Linux 上的 ELF、macOS 上的 Mach-O)的完全獨立的可執行文件,除了標準之外沒有任何外部依賴項 到該平臺(例如,libc)。而且它完全是原生的:看不到 IL,沒有 JIT,什么都沒有。所有必需的代碼都被編譯和/或鏈接到可執行文件中,包括與標準 .NET 應用程序和服務一起使用的相同 GC,以及圍繞線程等提供服務的最小運行時。
所有這些都帶來了巨大的好處:超快的啟動時間、小型且完全獨立的部署,以及在不允許 JIT 編譯器的地方運行的能力(因為無法執行可寫內存頁面)。它也帶來了限制:沒有 JIT 意味著沒有動態加載任意程序集(例如,Assembly.LoadFile)和沒有反射發射(例如,DynamicMethod),并且所有內容都被編譯并鏈接到應用程序中,這意味著使用更多功能(或可能使用 ) 并且您的部署可以更大。即使有這些限制,對于某些類別的應用程序,Native AOT 仍然是 .NET 7 中令人難以置信的、令人興奮和受歡迎的補充。
今天,Native AOT 專注于控制臺應用,那么我們來創建一個控制臺應用:
dotnet new console -o nativeaotexample
您現在有一個“Hello World”控制臺應用程序。要啟用使用Native AOT發布應用程序,請編輯 .csproj 以在現有 <PropertyGroup> 中包含以下內容:
<PublishAot>true</PublishAot>
該應用程序現在已完全配置為能夠以Native AOT為目標。剩下的就是發布了。如果要發布到 Windows x64 運行時,可以使用以下命令:
dotnet publish -r win-x64 -c Release
會在輸出發布目錄中生成一個可執行文件:
Directory: C:\nativeaotexample\bin\Release\net7.0\win-x64\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 8/27/2022 6:18 PM 3648512 nativeaotexample.exe
-a--- 8/27/2022 6:18 PM 14290944 nativeaotexample.pdb
大約 3.5MB 的 .exe 是可執行文件,它旁邊的 .pdb 是調試信息,部署應用程序不需要這些信息。現在,您可以將該 nativeaotexample.exe 復制到任何 64 位 Windows 機器上,應用程序都會運行。
▌支持 .NET 7
.NET 7 由 Microsoft 正式支持。它被標記為標準期限支持 (STS) 版本,將獲得 18 個月的支持。奇數 .NET 版本是 STS 版本,在隨后的 STS 或 LTS 版本之后的六個月內獲得免費支持和補丁。有關更多詳細信息,請參閱我們的 .NET 和 .NET Core 支持生命周期文檔。
.NET 和 .NET Core 支持生命周期文檔
https://dotnet.microsoft.com/platform/support/policy/dotnet-core
最后,我們想說:
沒有社區,.NET 就無法存在。.NET 項目是一個通過每個人的獨特和創造性貢獻的項目。這些偉大的成就和慷慨來自于我們社區每個人的支持和關懷。感謝您的參與、分享以及您對 .NET 社區的貢獻。
*未經授權請勿私自轉載此文章及圖片。
還在等什么?馬上下載 .NET 7。?
點擊「閱讀原文」下載 .NET 7~