宣布 .NET 6 — 迄今为止最快的 .NET
2021 年 11 月 8 日 (以下是译文)
欢迎使用 .NET 6。今天的发布是 .NET 团队和社区一年多来努力的结果。C# 10 和 F# 6 提供了语言改进,使您的代码更简单、更好。性能有了巨大的提升,我们已经看到降低了 Microsoft 托管云服务的成本。.NET 6 是第一个原生支持 Apple Silicon (Arm64) 的版本,并且还针对 Windows Arm64 进行了改进。我们构建了一个新的动态配置文件引导优化 (PGO) 系统,该系统可提供仅在运行时才可能进行的深度优化。使用dotnet monitor和OpenTelemetry改进了云诊断。WebAssembly支持更强大、更高效。添加了新的 API,用于HTTP/3,处理 JSON,数学,直接操作内存。.NET 6 将得到三年支持。开发人员已经开始将应用程序升级到 .NET 6,我们已经在生产中听到了很好的早期结果。.NET 6 已为您的应用做好准备。
您可以下载适用于 Linux、macOS 和 Windows 的.NET 6。
有关Web 方案的新增功能,请参阅ASP.NET Core帖子。
Visual Studio 2022 也在今天发布。阅读公告并观看发布活动以了解有关发布的更多信息。
PowerShell 7.2也在今天发布,基于 .NET 6。PowerShell 用户可以访问与 .NET 开发人员相同的性能改进和 API。
.NET Conf是一个为期三天的免费虚拟开发人员活动,旨在庆祝 .NET 的主要版本。它将于明天开始,并于 11 月 9 日至 11 日举行,届时将有来自我们团队、Microsoft 团队和更广泛社区的演讲者参加 80 多场会议。收听学习并与我们互动。
查看新的对话帖子,就最新的 .NET 功能进行工程师对工程师的深入讨论。
.NET 6 亮点
.NET 6 是:
使用Microsoft 服务、其他公司运行的云应用程序和开源项目进行了生产压力测试。
作为最新的长期支持 (LTS) 版本支持三年。
C# 10提供了语言改进,例如记录结构、隐式使用和新的 lambda 功能,同时编译器添加了增量源生成器。 F# 6添加了新功能,包括基于任务的异步、管道调试和众多性能改进。
Visual Basic在Visual Studio 体验和 Windows 窗体项目打开体验方面进行了改进。
热重载使您可以跳过重新构建和重新启动应用程序以查看新更改 - 在您的应用程序运行时 - 在 Visual Studio 2022 和 .NET CLI 中支持,适用于 C# 和 Visual Basic。
云诊断已通过OpenTelemetry和dotnet monitor得到改进,现在在生产中得到支持,并且可用于 Azure 应用服务。
JSON API的是更强大,并有一个源发生器用于串行更高的性能。
ASP.NET Core 中引入了最少的 API,以简化入门体验并提高 HTTP 服务的性能。
Blazor组件现在可以从 JavaScript 呈现并与现有的基于 JavaScript 的应用程序集成。
用于 Blazor WebAssembly (Wasm) 应用程序的WebAssembly AOT编译,以及对运行时重新链接和本机依赖项的支持。
使用http://ASP.NETCore 构建的单页应用程序现在使用更灵活的模式,可以与 Angular、React 和其他流行的前端 JavaScript 框架一起使用。
添加了 HTTP/3以便http://ASP.NETCore、HttpClient 和 gRPC 都可以与 HTTP/3 客户端和服务器交互。
文件 IO现在支持符号链接,并通过重新编写从头开始大大提高了性能FileStream。
通过支持OpenSSL 3、ChaCha20Poly1305 加密方案和运行时纵深防御缓解措施(特别是W^X和CET),安全性得到了提高。
可以为 Linux、macOS 和 Windows(以前仅适用于 Linux)发布单文件应用程序(免提取)。
IL 修整现在更加强大和有效,新的警告和分析器可确保最终结果正确。
添加了源生成器和分析器,可帮助您生成更好、更安全和更高性能的代码。
源代码构建使 Red Hat 等组织能够从源代码构建 .NET,并向用户提供自己的构建版本。
该版本包括大约一万个 git 提交。即使这篇文章很长,它也跳过了许多改进。您必须下载并试用 .NET 6 才能看到所有新内容。
支持
.NET 6 是一个长期支持 (LTS) 版本,将支持三年。它支持多种操作系统,包括 macOS Apple Silicon 和 Windows Arm64。
红帽与 .NET 团队合作,在红帽企业 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 2022和Visual Studio 2022 for Mac支持 .NET 6 。Visual Studio 2019、Visual Studio for Mac 8 或 MSBuild 16 不支持它。如果要使用 .NET 6,则需要升级到Visual Studio 2022(现在也是 64 位)。Visual Studio CodeC# 扩展支持 .NET 6 。
Azure 应用服务:
Azure Functions现在支持在 .NET 6 中运行无服务器函数。
该应用程序服务.NET 6 GA公告有兴奋http://ASP.NET核心开发人员的信息和细节得到今天.NET 6下去。
Azure 静态 Web 应用现在支持具有 Blazor WebAssembly 前端和 Azure Function API 的全栈 .NET 6 应用程序。
注意:如果您的应用已在应用服务上运行 .NET 6 预览版或 RC 构建,则一旦 .NET 6 运行时和 SDK 部署到您所在的区域,它将在第一次重新启动时自动更新。如果您部署了自包含应用程序,则需要重新构建和重新部署。
统一扩展平台
.NET 6 为浏览器、云、桌面、IoT和移动应用程序提供了一个统一的平台。底层平台已更新,以满足所有应用程序类型的需求,并使您可以轻松地在所有应用程序中重用代码。新功能和改进可同时用于所有应用程序,因此您在云中或移动设备上运行的代码具有相同的行为方式并具有相同的优势。
随着每个版本的发布,.NET 开发人员的影响范围不断扩大。机器学习和WebAssembly是最近添加的两个。例如,通过机器学习,您可以编写应用程序来查找流数据中的异常情况。使用 WebAssembly,您可以在浏览器中托管 .NET 应用程序,就像 HTML 和 JavaScript 一样,或者将它们与 HTML 和 JavaScript 混合使用。
最令人兴奋的新增功能之一是.NET 多平台应用程序 UI (.NET MAUI)。您现在可以在单个项目中编写代码,从而提供跨桌面和移动操作系统的现代客户端应用程序体验。.NET MAUI 的发布时间将比 .NET 6 晚一点。我们在 .NET MAUI 上投入了大量时间和精力,很高兴能够发布它并看到 .NET MAUI 应用程序投入生产。
当然,.NET 应用程序也可以在Windows 桌面上使用(使用Windows 窗体和WPF)以及在云中使用http://ASP.NETCore。它们是我们提供时间最长的应用程序类型,并且仍然非常受欢迎,我们在 .NET 6 中对它们进行了改进。
面向 .NET 6
继续以广泛的平台为主题,在所有这些操作系统上编写 .NET 代码很容易。
要以 .NET 6为目标,您需要使用 .NET 6 目标框架,如下所示:
net6.0
该目标框架名字对象(TFM),您可以访问所有的跨平台的API,.NET提供。如果您正在编写控制台应用程序、http://ASP.NETCore 应用程序或可重用的跨平台库,这是最佳选择。net6.0
如果您的目标是特定的操作系统(例如编写Windows 窗体或 iOS 应用程序),那么还有另一组 TFM(每个都针对一个不言而喻的操作系统)供您使用。它们使您可以访问所有 API以及一系列特定于操作系统的 API 。net6.0
net6.0-android
net6.0-ios
net6.0-maccatalyst
net6.0-tvos
net6.0-windows
每个无版本的 TFM 都相当于针对 .NET 6 支持的最低操作系统版本。如果您想要特定或访问更新的 API,可以指定操作系统版本。
的和TFMS支持(同.NET 5)。Android 和 Apple TFM 是 .NET 6 的新增功能,目前处于预览阶段。稍后的 .NET 6 更新将支持它们。net6.0net6.0-windows
操作系统特定的 TFM 之间没有兼容性关系。例如,与. 如果您想共享代码,您需要使用带有语句的源代码或带有目标代码的二进制文件来实现。net6.0-iosnet6.0-tvos#ifnet6.0
表现
自从我们启动 .NET Core 项目以来,该团队就一直非常关注性能。Stephen Toub在捕捉每个版本的 .NET 性能进展方面做得非常出色。如果您还没有机会,我建议您查看他在 .NET 6 中的性能改进帖子。
在这篇博文中,我收集了一些您想了解的重大性能改进,包括文件 IO、界面转换、PGO 和 System.Text.Json。
动态 PGO
动态轮廓引导优化 (PGO)可以显着提高稳态性能。例如,PGO 使 TechEmpower JSON“MVC”套件的每秒请求数提高了 26%(510K -> 640K)。
动态 PGO 建立在分层编译之上,它使方法能够首先非常快速地编译(称为“第 0 层”)以提高启动性能,然后在启用大量优化的情况下随后重新编译(称为“第 1 层”)一旦这种方法被证明是有效的。该模型使方法能够在第 0 层中进行检测,以允许对代码的执行进行各种观察。当这些方法在第 1 层重新编译时,从第 0 层执行中收集的信息将用于更好地优化第 1 层代码。这就是机制的本质。
动态 PGO 的启动时间将比默认运行时稍慢,因为在第 0 层方法中运行额外的代码来观察方法行为。
要启用动态 PGO,请在您的应用程序将运行的环境中进行设置。您还必须确保启用分层编译(默认情况下)。动态 PGO 是可选的,因为它是一种新的、有影响力的技术。我们希望发布选择性使用和相关反馈,以确保它经过全面压力测试。我们对分层编译做了同样的事情。至少一个非常大的 Microsoft 服务支持动态 PGO,并且已经在生产中使用它。我们鼓励您尝试一下。DOTNET_TieredPGO=1
您可以在 .NET 6中的性能博文中看到更多关于动态 PGO 优势的信息,包括以下微基准测试,它测量特定 LINQ 枚举器的成本。
private IEnumerator _source = Enumerable.Range(0, long.MaxValue).GetEnumerator(); [Benchmark] public void MoveNext() => _source.MoveNext();
这是有和没有动态 PGO 的结果。
这是一个相当大的差异,但也增加了代码大小,这可能会让一些读者感到惊讶。这是 JIT 生成的汇编代码的大小,而不是内存分配(这是一个更常见的焦点)。.NET 6 Performance 帖子对此有很好的解释。
PGO 实现中常见的一种优化是“热/冷拆分”,其中经常执行的方法部分(“热”)在方法开始时靠近在一起,而不经常执行的方法部分(“冷”)被移到一起移动到方法的末尾。这可以更好地使用指令缓存并最大限度地减少可能未使用的代码的负载。
作为上下文,接口调度是 .NET 中最昂贵的调用类型。非虚拟方法调用是最快的,甚至更快的是可以通过内联消除的调用。在这种情况下,动态 PGO 为MoveNext. 第一个 — 热的 — 是直接调用,另一个 — 冷的 — 是通过. 如果最热门的人大部分时间都被跟注,那将是一场巨大的胜利。Enumerable+RangeIterator.MoveNextIEnumerator
这就是魔法。当 JIT 检测此方法的第 0 层代码时,包括检测此接口分派以跟踪_source每次调用的具体类型。并且 JIT 发现每次调用都在一个名为 的类型上,这是一个用于在实现内部实现的私有类。因此,对于第 1 层,JIT 已发出检查以查看类型是否为:如果不是,则它跳转到我们之前强调的执行正常接口调度的冷部分。但如果是——基于分析数据预计在绝大多数时间都是这种情况——然后它可以继续直接调用Enumerable+RangeIteratorEnumerable.RangeEnumerable_sourceEnumerable+RangeIteratorEnumerable+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 倍的分配下降:
我们还认识到需要更多高性能文件 IO 功能:并发读写和分散/聚集 IO。针对这些情况,我们为和类引入了新的 API 。System.IO.FileSystem.IO.RandomAccess
async Task AllOrNothingAsync(string path, IReadOnlyList 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! }
示例演示:
使用新API打开文件句柄。File.OpenHandle
使用新的预分配大小功能预分配磁盘空间。
使用新的Scatter/Gather IOAPI写入文件。
预分配大小功能提高了性能,因为写入操作不需要扩展文件,而且文件碎片化的可能性较小。这种方法提高了可靠性,因为写操作将不再因空间不足而失败,因为空间已经被保留。Scatter/Gather IO API 减少了写入数据所需的系统调用次数。
更快的界面检查和转换
界面投射性能提升了 16% – 38%。这种改进对于 C# 与接口之间的模式匹配特别有用。
该图表展示了代表性基准的改进规模。
将 .NET 运行时的一部分从 C++ 迁移到托管 C# 的最大优势之一是它降低了贡献的障碍。这包括接口转换,它作为 .NET 6 的早期更改移至 C#。.NET 生态系统中通晓 C# 的人多于 C++(并且运行时使用具有挑战性的 C++ 模式)。能够阅读组成运行时的一些代码是培养对以各种形式贡献的信心的重要一步。
归功于本·亚当斯。
System.Text.Json 源代码生成器
我们为 System.Text.Json添加了一个源代码生成器,它避免了在运行时进行反射和代码生成的需要,并且可以在构建时生成最佳序列化代码。序列化程序通常使用非常保守的技术编写,因为它们必须如此。但是,如果您阅读自己的序列化源代码(使用序列化程序),您会看到哪些明显的选择可以使序列化程序在您的特定情况下更加优化。这正是这个新的源生成器所做的。
除了提高性能和减少内存之外,源代码生成器还可以生成最适合程序集修整的代码。这有助于开发更小的应用程序。
序列化POCO是一个非常常见的场景。使用新源发生器,我们观察到,序列化是〜1.6倍快与我们的基准。
该TechEmpower缓存基准行使平台或从数据库来源的信息架构的内存缓存。基准测试的 .NET 实现对缓存数据执行 JSON 序列化,以便将其作为对测试工具的响应发送。
我们观察到~100K RPS 增益(~40% 增加)。与MemoryCache 性能改进相结合时,.NET 6 的吞吐量比 .NET 5 高 50% !
C# 10
欢迎使用 C# 10。C# 10 的一个主要主题是继续从C# 9 中的顶级语句开始的简化之旅。新功能从 中删除了更多的仪式,导致程序短至一行。他们的灵感来自与没有 C# 经验的人(学生、专业开发人员和其他人)交谈,并学习对他们来说最有效且直观的方法。Program.cs
大多数.NET SDK 模板已经更新,以提供 C# 10 现在可以实现的更简单、更简洁的体验。我们听到反馈说有些人不喜欢新模板,因为它们不是为专家设计的,删除面向对象,删除在编写 C# 的第一天就需要学习的重要概念,或鼓励在一个文件中编写整个程序。客观地说,这些观点都不是真的。新模型同样适用于学生和专业开发人员。然而,它与我们在 .NET 6 之前拥有的 C 派生模型不同。
C# 10 中还有其他一些功能和改进,包括记录结构。
全局 using 指令
全局 using 指令让您using只需指定一次指令,并将其应用于您编译的每个文件。
以下示例显示了语法的广度:
global using System;
global using static System.Console;
global using Env = System.Environment;
您可以将语句放在任何文件中,包括在.global using.csProgram.cs
隐式 using 是一个 MSBuild 概念,它根据 SDK自动添加一组指令。例如,控制台应用隐式使用不同于http://ASP.NETCore。global using
隐式使用是选择加入的,并在以下位置启用PropertyGroup:
enable
隐式使用是现有项目的选择加入,但默认情况下包含在新的 C# 项目中。有关更多信息,请参阅隐式使用。
文件范围的命名空间
文件范围的命名空间使您可以为整个文件声明命名空间,而无需将其余内容嵌套在. 只允许一个,并且它必须出现在声明任何类型之前。{ ... }
新语法是一行:
namespace MyNamespace; class MyClass { ... } // Not indented
这种新语法是三行缩进样式的替代:
namespace MyNamespace { class MyClass { ... } // Everything is indented }
好处是在整个文件都在同一个命名空间中的极其常见的情况下减少缩进。
记录结构
C# 9 引入了记录作为类的一种特殊的面向值的形式。在 C# 10 中,您还可以声明结构记录。C# 中的结构已经具有值相等性,但记录结构添加了一个==运算符和一个 的实现,以及一个基于值的实现:IEquatable
public record struct Person { public string FirstName { get; init; } public string LastName { get; init; } }
就像记录类一样,记录结构可以是“位置”的,这意味着它们有一个主构造函数,它隐式声明了与参数对应的公共成员:
public record struct Person(string FirstName, string LastName);
但是,与记录类不同,隐式公共成员是可变的自动实现的属性。这是因为记录结构是元组的自然成长故事。例如,如果您有一个返回类型,并且您想将其扩展为命名类型,您可以轻松声明相应的位置结构记录并维护可变语义。(string FirstName, string LastName)
如果您想要一个具有只读属性的不可变记录,您可以声明整个记录结构readonly(就像其他结构一样):
public readonly record struct Person(string FirstName, string LastName);
C# 10 不仅支持with记录结构的表达式,还支持所有结构以及匿名类型的表达式:
var updatedPerson = person with { FirstName = "Mary" };
F# 6
F# 6旨在让 F# 更简单、更高效。这适用于语言设计、库和工具。我们对 F# 6(及更高版本)的目标是消除语言中让用户感到惊讶或对学习 F# 造成障碍的极端情况。我们很高兴与 F# 社区合作进行这项持续的工作。
使 F# 更快、更具互操作性
新语法直接创建一个任务并启动它。这是 F# 6 中最重要的特性之一,它使异步任务更简单、性能更高,并且与 C# 和其他 .NET 语言的互操作性更强。以前,创建 .NET 任务需要使用创建任务和调用.task {…}async {…}Async.StartImmediateAsTask
该功能建立在称为“可恢复代码”RFC FS-1087的基础之上。可恢复代码是一个核心特性,我们希望在未来使用它来构建其他高性能异步和屈服状态机。task {…}
F# 6 还为库作者添加了其他性能特性,包括InlineIfLambdaF# 活动模式的未装箱表示。一个特别显着的性能改进是在列表和数组表达式的编译中,现在它们的速度提高了4 倍,并且调试也更好更简单。
使 F# 更易于学习且更统一
F# 6 启用索引语法。到目前为止,F# 一直使用 expr.[idx] 进行索引。删除点符号是基于第一次使用 F# 用户的反复反馈,即使用点是与他们期望的标准实践的不必要的分歧。在新代码中,我们建议系统地使用新的索引语法。作为一个社区,我们都应该改用这种语法。expr[idx]expr[idx]
F# 社区做出了重要改进,使 F# 语言在 F# 6 中更加统一。其中最重要的是消除了 F# 缩进规则中的许多不一致和限制。其他使 F# 更加统一的设计添加包括添加as模式;在计算表达式中允许“重载自定义操作”(对 DSL 有用);允许_丢弃use绑定并允许输出中的二进制格式。F# 核心库添加了用于对列表、数组和序列进行复制和更新的新函数,以及其他内在函数。从 2.0 开始不推荐使用的 F# 的一些遗留功能现在会导致错误。其中许多更改更好地使 F# 符合您的期望,从而减少了意外。%BNativePtr
F# 6 还添加了对 F# 中其他“隐式”和“类型导向”转换的支持。这意味着更少的显式向上转换,并增加了对 .NET 样式隐式转换的一流支持。F# 还进行了调整,以更好地适应使用 64 位整数的数字库时代,并对 32 位整数进行隐式加宽。
改进 F# 工具
F# 6 中的工具改进使日常编码变得更容易。新的“管道调试”允许您单步执行、设置断点并检查 F# 管道语法的中间值。阴影值的调试显示已得到改进,消除了调试时常见的混淆源。现在,F# 工具的性能也更高,F# 编译器并行执行解析阶段。F# IDE 工具也得到了改进。F# 脚本现在更加强大,允许您通过文件固定使用的 .NET SDK 版本。input |> f1 |> f2global.json
热重载
热重载是另一个性能特性,专注于开发人员的生产力。它使您能够对正在运行的应用程序进行各种代码编辑,从而减少您等待应用程序重新构建、重新启动或重新导航到进行代码更改后所在位置所需的时间。
热重载可通过dotnet watchCLI 工具和 Visual Studio 2022 使用。您可以将热重载用于多种应用程序类型,例如 ASP.NET Core、Blazor、.NET MAUI、控制台、Windows 窗体 (WinForms)、WPF、WinUI 3、Azure 函数等。
使用 CLI 时,只需使用 启动您的 .NET 6 应用程序dotnet watch,进行任何支持的编辑,并在保存文件时(如在 Visual Studio Code 中)将立即应用这些更改。如果不支持更改,详细信息将记录到命令窗口。
此图像显示正在启动的 MVC 应用程序dotnet watch。我对和文件进行了编辑(如日志中所报告的那样),并且两者都被应用到代码中并在不到半秒的时间内很快地反映在浏览器中。.cs.cshtml
使用 Visual Studio 2022 时,只需启动您的应用程序,进行受支持的更改,然后使用新的“热重载”按钮(如下图所示)应用这些更改。您还可以选择通过同一按钮上的下拉菜单在保存时应用更改。使用 Visual Studio 2022 时,Hot Reload 可用于多个 .NET 版本、.NET 5+、.NET Core 和 .NET Framework。例如,您将能够对OnClickEvent按钮的处理程序进行代码隐藏更改。Main应用程序的方法不支持它。
注意:RuntimeInformation.FrameworkDescription中存在一个错误,该错误在该图像中显示,将很快修复。
热重载还与现有的“编辑并继续”功能(在断点处停止时)和 XAML 热重载协同工作,用于实时编辑应用程序 UI。目前支持 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。大多数发行版还没有这样做。例如,如果您在 Red Hat 8 或 Ubuntu 20.04 上安装 .NET 6,您将不会(在撰写本文时)开始使用 OpenSSL 3。
OpenSSL 3、Windows 10 21H1 和 Windows Server 2022 都支持ChaCha20Poly1305。您可以在 .NET 6 中使用这种新的经过身份验证的加密方案(假设您的环境支持它)。
感谢凯文-琼斯为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 运行,例如. 相同的论点适用于其他 CLI 动词。有关详细信息,请参阅适用于 macOS 和 Windows Arm64 的 .NET 6 RC2 更新。-adotnet run -a x64
我想确保涵盖了一个微妙之处。当您使用 时,SDK 仍以 Arm64 的形式在本机运行。.NET SDK体系结构中存在进程边界存在的固定点。大多数情况下,一个进程必须全是 Arm64 或全是 x64。我稍微简化了一点,但 .NET CLI 会等待 SDK 架构中的最后一个进程创建,然后将它作为您请求的芯片架构启动,例如 x64。这就是您的代码运行的过程。这样,您作为开发人员可以获得 Arm64 的好处,但您的代码可以在它需要的过程中运行。这仅在您需要以 x64 格式运行某些代码时才相关。如果你不这样做,那么你可以一直以 Arm64 的方式运行一切,这很好。-a x64
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 也不需要它们。
Windows Arm64
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 上本机运行。我将向您展示http://ASP.NETCore 的外观。
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 的默认执行,以及使用参数在面向 Arm64 和 x64 之间切换是多么容易。完全相同的体验适用于 Windows Arm64。-a
此图像演示了相同的内容,但使用http://ASP.NETCore。我使用的 .NET 6 Arm64 SDK 与您在上图中看到的相同。
Arm64 上的 Docker
Docker 支持在本机架构和仿真中运行的容器,本机架构是默认的。这看起来很明显,但当大多数 Docker Hub 目录面向 x64 时可能会令人困惑。您可以使用来请求 x64 图像。--platform linux/amd64
我们仅支持在 Arm64 操作系统上运行 Linux Arm64 .NET 容器映像。这是因为我们从来不支持在QEMU 中运行 .NET ,这是 Docker 用于架构模拟的。看来这可能是由于 QEMU 的限制。
此图片展示了我们维护控制台例子:。这是一个有趣的示例,因为它包含一些用于打印 CPU 和内存限制信息的基本逻辑,您可以使用它们。我展示的图像设置了 CPU 和内存限制。mcr.microsoft.com/dotnet/samples
自己试试: 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 Docker
.NET 6 添加了对 Windows 进程隔离容器的支持。如果您在 Azure Kubernetes 服务 (AKS) 中使用Windows 容器,则您依赖于进程隔离的容器。进程隔离容器可以被认为与 Linux 容器非常相似。Linux 容器使用cgroups,Windows 进程隔离容器使用Job Objects。Windows 还提供 Hyper-V 容器,它通过更大的虚拟化提供更大的隔离。Hyper-V 容器在 .NET 6 中没有变化。
此更改的主要价值是现在将报告 Windows 进程隔离容器的正确值。如果在 64 核机器上创建 2 核容器,将返回. 在以前的版本中,此属性将报告机器上的处理器总数,与 Docker CLI、Kubernetes 或其他容器编排器/运行时指定的限制无关。该值被 .NET 的各个部分用于缩放目的,包括 .NET 垃圾收集器(尽管它依赖于相关的较低级别的 API)。社区库也依赖此 API 进行扩展。Environment.ProcessorCountEnvironment.ProcessorCount2
我们最近在 AKS 上使用大量 Pod 生产的 Windows 容器上与客户一起验证了这项新功能。他们能够用50%的内存运行成功(相比,其典型的配置),先前导致了水平OutOfMemoryException和StackOverflowException例外。他们没有花时间找到最小内存配置,但我们猜测它明显低于他们典型内存配置的 50%。由于这一变化,他们将转向更便宜的 Azure 配置,从而节省资金。这是一个不错的、轻松的胜利,只需升级即可。
优化缩放
我们从用户那里听到一些应用程序在报告正确值时无法实现最佳缩放。如果这听起来与您刚刚阅读的 Windows Containers 内容相反,那么它有点像。.NET 6 现在提供DOTNET_PROCESSOR_COUNT 环境变量来手动控制 的值。在典型用例中,应用程序可能在 64 核机器上配置有 4 个核,并且在 8 核或 16 核方面具有最佳扩展性。此环境变量可用于启用该缩放。Environment.ProcessorCountEnvironment.ProcessorCount
这个模型可能看起来很奇怪,其中和(通过 Docker CLI)值可能不同。默认情况下,容器运行时面向核心等效项,而不是实际核心。这意味着,当您说需要 4 个内核时,您将获得 4 个内核的等效 CPU 时间,但是您的应用程序可能(理论上)在更多内核上运行,甚至在短时间内在 64 核机器上运行所有 64 个内核。这可能会使您的应用程序在 4 个以上的线程上更好地扩展(继续示例),并且分配更多可能是有益的。这假设线程分配基于 的值。如果您选择设置更高的值,您的应用可能会使用更多内存。对于某些工作负载,这是一个简单的权衡。至少,这是一个您可以测试的新选项。Environment.ProcessorCount--cpusEnvironment.ProcessorCount
Linux 和 Windows 容器均支持此新功能。
Docker 还提供了 CPU 组功能,您的应用程序可以关联到特定的内核。在这种情况下不推荐使用此功能,因为应用程序可以访问的内核数量是具体定义的。我们还看到了将它与 Hyper-V 容器一起使用时的一些问题,它并不是真正适用于这种隔离模式。
Debian 11 “靶心”
我们密切关注Linux 发行版的生命周期和发布计划,并尝试代表您做出最佳选择。Debian 是我们用于默认 Linux 映像的 Linux 发行版。如果您6.0从我们的一个容器存储库中提取标签,您将提取一个 Debian 映像(假设您使用的是 Linux 容器)。对于每个新的 .NET 版本,我们都会考虑是否应该采用新的 Debian 版本。
作为政策问题,我们不会为了我们的便利标签而更改 Debian 版本,例如6.0中期发布。如果我们这样做了,某些应用程序肯定会崩溃。这意味着,在发布之初选择 Debian 版本非常重要。此外,这些图像得到了很多使用,主要是因为它们被“好标签”引用。
Debian 和 .NET 版本自然不是一起计划的。当我们开始 .NET 6 时,我们看到 Debian “bullseye”可能会在 2021 年发布。我们决定从发布之初就押注于 Bullseye。我们开始使用.NET 6 Preview 1发布基于 Bullseye 的容器映像,并决定不再回头。赌注是 .NET 6 版本将在与 Bullseye 版本的竞争中失败。到 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是容器的重要诊断工具。它作为 sidecar 容器镜像已经有一段时间了,但处于不受支持的“实验”状态。作为 .NET 6 的一部分,我们将发布一个完全支持生产的基于 .NET 6 的dotnet monitor映像。
dotnet monitor已被 Azure 应用服务用作其http://ASP.NETCore 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通过非互联网可寻址的unix 域套接字与运行时交互——跨越容器边界。该模型通信模型非常适合此用例。
结构化JSON 日志
该JSON格式现在是默认控制台记录f="https://hub.docker.com/_/microsoft-dotnet-aspnet">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.Metrics是OpenTelemetry Metrics API 规范的 .NET 实现。Metrics API 是专门为处理原始测量而设计的,目的是高效且同时地生成这些测量的连续摘要。
API 包括Meter可用于创建仪器对象的类。这些API暴露四个仪器类:Counter,Histogram,ObservableCounter,并ObservableGauge支持不同的指标方案。此外,API 公开MeterListener该类以允许侦听仪器的记录测量以进行聚合和分组。
该OpenTelemetry .NET实现将扩展到使用这些新的API,其中新增的指标可观察方案的支持。
库测量记录示例
Meter meter = new Meter("io.opentelemetry.contrib.mongodb", "v1.0"); Counter counter = meter.CreateCounter("Requests"); counter.Add(1); counter.Add(1, KeyValuePair.Create("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((instrument, measurement, tags, state) => { Console.WriteLine($"Instrument: {instrument.Name} has recorded the measurement {measurement}"); }); listener.Start();
Windows 窗体
我们继续在 Windows 窗体中进行关键改进。.NET 6 包括更好的控件可访问性、设置应用程序范围默认字体的能力、模板更新等。
辅助功能改进
在此版本中,我们增加了UIA提供商为CheckedListBox,LinkLabel,Panel,ScrollBar,TabControl和TrackBar,使像旁白工具和测试自动化与应用程序的元素进行交互。
默认字体
现在,您可以设置默认字体的应用程序有。Application.SetDefaultFont
void Application.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(new Font(...)); Application.SetHighDpiMode(HighDpiMode.SystemAware);
这些调用的参数可通过csproj 或 props 文件中的MSBuild 属性进行配置。
Visual Studio 2022 中的 Windows 窗体设计器也知道这些属性(目前它只读取默认字体),并且可以向您展示您的应用程序,就像它在运行时一样:
模板更新
C# 的 Windows 窗体模板已更新,以支持新的应用程序引导程序、指令、文件范围的命名空间和可为空的引用类型。global using
更多运行时设计器
现在您可以构建通用设计器(例如,报表设计器),因为 .NET 6 具有设计器和设计器相关基础结构的所有缺失部分。有关更多信息,请参阅此博客文章。
单文件应用程序
在 .NET 6 中,已为 Windows 和 macOS 启用内存中单文件应用程序。在 .NET 5 中,这种部署类型仅限于 Linux。您现在可以为所有支持的操作系统发布作为单个文件部署和启动的单文件二进制文件。单文件应用程序不再将任何核心运行时程序集提取到临时目录。
这种扩展功能基于称为“超级主机”的构建块。“apphost”是在非单文件情况下启动应用程序的可执行文件,例如或。Apphost 包含用于查找运行时、加载它并使用该运行时启动您的应用程序的代码。Superhost 仍然执行其中一些任务,但使用所有 CoreCLR 本机二进制文件的静态链接副本。静态链接是我们用来启用单个文件体验的方法。myapp.exe./myapp
本机依赖项(如 NuGet 包附带的依赖项)是单文件嵌入的显着例外。默认情况下,它们不包含在单个文件中。例如,WPF 本机依赖项不是超级主机的一部分,导致除了单文件应用程序之外还有其他文件。您可以使用该设置IncludeNativeLibrariesForSelfExtract来嵌入和提取本机依赖项。
静态分析
我们改进了单文件分析器以允许自定义警告。如果您的 API 在单文件发布中不起作用,您现在可以使用该属性对其进行标记,如果启用了分析器,则会出现警告。添加该属性还将使方法中与单个文件相关的所有警告静音,因此您可以使用该警告将警告向上传播到您的公共 API。[RequiresAssemblyFiles]
设置为 时,会自动为exe项目启用单文件分析器,但您也可以通过设置为为任何项目启用它。如果您想支持库作为单个文件应用程序的一部分,这会很有帮助。PublishSingleFiletrueEnableSingleFileAnalysistrue
在 .NET 5 中,我们添加了警告和其他一些在单文件包中表现不同的 API。Assembly.Location
压缩
单文件包现在支持压缩,可以通过将属性设置EnableCompressionInSingleFile为true. 在运行时,文件会根据需要解压缩到内存中。压缩可以为某些场景提供巨大的空间节省。
让我们看一下与NuGet 包资源管理器一起使用的带压缩和不带压缩的单个文件发布。
无压缩:172 MB
压缩后:71.6 MB
压缩可以显着增加应用程序的启动时间,尤其是在 Unix 平台上。Unix 平台具有无法与压缩一起使用的无复制快速启动路径。您应该在启用压缩后测试您的应用,看看额外的启动成本是否可以接受。
单文件调试
单文件应用程序目前只能使用平台调试器(如 WinDBG)进行调试。我们正在考虑在 Visual Studio 2022 的更高版本中添加 Visual Studio 调试。
macOS 上的单文件签名
单文件应用程序现在满足 macOS 上的 Apple 公证和签名要求。在具体的变化涉及到我们在离散文件布局方面构建单个文件的应用程序的方式。
苹果开始执行新规定的签署和公证与MacOS的卡特琳娜。我们一直在与 Apple 密切合作,以了解要求,并寻找解决方案,使 .NET 等开发平台能够在该环境中良好运行。在最近的几个 .NET 版本中,我们已经对产品进行了更改并记录了用户工作流程,以满足 Apple 的要求。剩下的差距之一是单文件签名,这是在 macOS 上分发 .NET 应用程序的要求,包括在 macOS 商店中。
IL 修整
该团队一直致力于为多个版本进行 IL 修整。.NET 6 代表了这一旅程的重要一步。我们一直在努力使更激进的修剪模式安全且可预测,因此有信心将其设为默认值。以前是选择加入功能,现在是默认功能。TrimMode=link
我们有一个三管齐下的修剪策略:
提高平台的修剪能力。
对平台进行注释以提供更好的警告并使其他人也能这样做。
在此基础上,使默认修剪模式更加激进,以便轻松将应用程序变小。
由于使用未注释反射的应用程序的结果不可靠,因此修剪之前一直处于预览状态。有了修剪警告,体验现在应该是可预测的。没有修剪警告的应用程序应该正确修剪并且在运行时观察到行为没有变化。目前,只有核心 .NET 库已经完全注释用于修剪,但我们希望看到生态系统注释用于修剪并成为修剪兼容
减少应用程序大小
让我们来看看使用crossgen 的这种修剪改进,它是 SDK 工具之一。可以只用一些修剪警告来修剪它,crossgen 团队能够解决这些问题。
首先,让我们将 crossgen 发布为一个独立的应用程序,无需修剪。它是 80 MB(包括 .NET 运行时和所有库)。
然后我们可以尝试(现在是旧版).NET 5 默认修剪模式,copyused. 结果下降到 55 MB。
新的 .NET 6 默认修剪模式link将自包含文件大小进一步降低到 36MB。
我们希望新的link修剪模式能更好地满足修剪的期望:显着的节省和可预测的结果。
默认启用警告
修剪警告告诉您修剪可能会删除运行时使用的代码的地方。这些警告以前在默认情况下被禁用,因为警告非常嘈杂,主要是由于 .NET 平台没有作为第一类场景参与修剪。
我们对 .NET 库的大部分进行了注释,以便它们生成准确的修剪警告。因此,我们认为是时候默认启用修剪警告了。http://ASP.NETCore 和 Windows 桌面运行时库尚未注释。我们计划接下来(.NET 6 之后)注释http://ASP.NET服务组件。我们希望看到社区在 .NET 6 发布后对 NuGet 库进行注释。
您可以通过设置
更多信息:
与本机 AOT 共享
我们也为Native AOT 实验实施了相同的修剪警告,这应该会以大致相同的方式改善 Native AOT 编译体验。
数学
我们显着改进了数学 API。社区中的一些人已经在享受这些改进。
面向性能的 API
面向性能的数学 API 已添加到 System.Math。如果底层硬件支持,它们的实现是硬件加速的。
新 API:
SinCos用于同时计算Sin和Cos。
ReciprocalEstimate用于计算 的近似值。1 / x
ReciprocalSqrtEstimate用于计算 的近似值。1 / Sqrt(x)
新的重载:
Clamp、DivRem、Min、 和Max支持nint和nuint。
Abs和Sign支持nint。
DivRem返回 a 的变体tuple。
性能改进:
大整数性能
从十进制和十六进制字符串解析 BigIntegers已得到改进。我们看到了高达 89% 的改进,如下图所示(越低越好)。
归功于约瑟夫·达席尔瓦。
Complex API 现在注释为 readonly
href="https://github.com/dotnet/runtime/pull/51797/">现在System.Numerics.Complexreadonly对各种API 进行了注释,以确保不会为readonly通过in.
感谢hrrrrustic。
BitConverter 现在支持浮点到无符号整数比特转换
BitConverter ref="https://github.com/dotnet/runtime/pull/53784">现在支持DoubleToUInt64Bits,HalfToUInt16Bits,SingleToUInt32Bits,UInt16BitsToHalf,UInt32BitsToSingle,和UInt64BitsToDouble。这应该可以在需要时更容易地进行浮点位操作。
感谢米哈尔Petryka。
BitOperations 支持附加功能
BitOperations现在支持IsPow2、RoundUpToPowerOf2并为现有函数href="https://github.com/dotnet/runtime/pull/58733">提供nint/nuint重载。
Vector , Vector2, Vector3, 和Vector4改进
Vector
Vector
Vector
ref="https://github.com/dotnet/runtime/pull/50062">重载支持Span
更好地解析标准数字格式
我们改进了标准数字类型的解析器,特别是和。他们现在将理解精度要求 > 99 位小数,并将提供精确到那么多位数的结果。此外,解析器现在更好地支持方法中的尾随零。.ToString.TryFormatParse
以下示例演示了前后行为。
32.ToString("C100") -> C132
.NET 6: $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
系统.文本.Json
System.Text.Json提供了多种高性能的 API 来处理 JSON 文档。在过去的几个版本中,我们添加了新功能,以进一步提高 JSON 处理性能并减轻想要从. 此版本包括在这条道路上的继续,并且是性能的重大进步,特别是在序列化器源生成器方面。NewtonSoft.Json
JsonSerializer 源代码生成
注意:应重新编译使用 .NET 6 RC1 或更早版本的源代码生成的应用程序。
几乎所有 .NET 序列化程序的支柱都是反射。反射对于某些场景来说是一项很棒的功能,但不能作为高性能云原生应用程序(通常(反)序列化和处理大量 JSON 文档)的基础。反射是启动、内存使用和程序集修整的问题。
运行时反射的替代方案是编译时源代码生成。在 .NET 6 中,我们将一个"https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/">新的源生成器作为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
现在,可以(反)序列化JSON阵列IAsyncEnumerable
JsonSerializer.SerializeAsync已更新以识别IAsyncEnumerable值并提供特殊处理。
using System; using System.Collections.Generic; using System.IO; using System.Text.Json; static async IAsyncEnumerable 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
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(stream)) { Console.WriteLine(item); }
此示例将按需反序列化元素,并且在使用特别大的数据流时非常有用。它只支持从根级 JSON 数组中读取,尽管将来可能会根据反馈放宽。
现有DeserializeAsync方法名义上支持,但在其非流式方法签名的范围内。它必须将最终结果作为单个值返回,如下例所示。IAsyncEnumerable
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(stream); await foreach (int item in result.Data) { Console.WriteLine(item); } public class MyPoco { public IAsyncEnumerable Data { get; set; } }
在此示例中,反序列化器将IAsyncEnumerable在返回反序列化对象之前缓冲内存中的所有内容。这是因为反序列化器需要在返回结果之前消耗整个 JSON 值。
System.Text.Json:可写 DOM 功能
在写JSON DOM功能增加了一个新的简单的,高性能的编程模型的。这个新的 API 很有吸引力,因为它避免了需要强类型的序列化契约,并且与现有类型相比,DOM 是可变的。System.Text.JsonJsonDocument
这个新的 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(); 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(); 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() == 11);
ReferenceHandler.IgnoreCycles
JsonSerializer(System.Text.Json)现在支持在序列化对象图时忽略循环的能力。该选项的行为与 Newtonsoft.Json 相似。一个主要区别是 System.Text.Json 实现用JSON 标记替换引用循环,而不是忽略对象引用。ReferenceHandler.IgnoreCyclesReferenceLoopHandling.Ignorenull
您可以在以下示例中看到 的行为。在这种情况下,属性被序列化,因为它否则会创建一个循环。ReferenceHandler.IgnoreCyclesNextnull
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 用户来说,此功能非常重要。红帽告诉我们,.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的扩展实现,RFC 7692。它允许使用该算法压缩 WebSockets 消息有效负载。此功能是用户对 GitHub 上 Networking 的最高要求之一。permessage-deflateDEFLATE
与加密一起使用的压缩可能会导致攻击,例如CRIME和BREACH。这意味着不能在单个压缩上下文中将秘密与用户生成的数据一起发送,否则可以提取该秘密。为了让用户注意这些影响并帮助他们权衡风险,我们将其中一个关键 API 命名为DangerousDeflateOptions。我们还添加了对特定消息关闭压缩的功能,因此如果用户想要发送机密,他们可以在不压缩的情况下安全地发送。
禁用压缩时 WebSocket的内存占用减少了约 27%。
从客户端启用压缩很容易,如下例所示。但是,请记住,服务器可以协商设置,例如请求较小的窗口或完全拒绝压缩。
var cws = new ClientWebSocket(); cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions() { ClientMaxWindowBits = 10, ServerMaxWindowBits = 10 };
还添加了对 ASP.NET Core 的 WebSocket 压缩支持。
感谢伊万兹拉塔诺夫。
袜子代理支持
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);
感谢霍耀元。
Microsoft.Extensions.Hosting — ConfigureHostOptions 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(o => { o.ShutdownTimeout = TimeSpan.FromMinutes(10); }); }) .Build(); host.Run();
Microsoft.Extensions.DependencyInjection — CreateAsyncScope API
该CreateAsyncScopeAPI是为了处理处置IAsyncDisposable服务。以前,您可能已经注意到,对IAsyncDisposable服务提供者的处置可能会引发InvalidOperationException异常。
下面的示例演示了新模式,CreateAsyncScope用于启用using语句的安全使用。
await using (var scope = provider.CreateAsyncScope()) { var foo = scope.ServiceProvider.GetRequiredService(); }
下面的例子演示了现有的问题案例:
using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; await using var provider = new ServiceCollection() .AddScoped() .BuildServiceProvider(); // This using can throw InvalidOperationException using (var scope = provider.CreateScope()) { var foo = scope.ServiceProvider.GetRequiredService(); } class Foo : IAsyncDisposable { public ValueTask DisposeAsync() => default; }
以下模式是之前建议的避免异常的解决方法。不再需要它。
var scope = provider.CreateScope(); var foo = scope.ServiceProvider.GetRequiredService(); await ((IAsyncDisposable)scope).DisposeAsync();
感谢马丁Björkström。
Microsoft.Extensions.Logging — 编译时源代码生成器
.NET 6href="https://github.com/dotnet/runtime/issues/52549">引入了LoggerMessageAttribute类型. 此属性是命名空间的一部分,使用时,它会源生成高性能日志 API。源代码生成日志支持旨在为现代 .NET 应用程序提供高度可用和高性能的日志解决方案。自动生成的源代码依赖于接口和功能。Microsoft.Extensions.LoggingILoggerLoggerMessage.Define
当LoggerMessageAttribute用于partial日志记录方法时触发源生成器。当被触发时,它要么能够自动生成partial它正在装饰的方法的实现,要么生成带有正确使用提示的编译时诊断。编译时日志记录解决方案在运行时通常比现有日志记录方法快得多。它通过最大限度地消除装箱、临时分配和副本来实现这一点。
与直接手动使用API 相比,有以下好处:LoggerMessage.Define
更短更简单的语法:声明性属性使用而不是编码样板。
引导开发者体验:生成器给出警告,帮助开发者做正确的事。
支持任意数量的日志参数。最多支持六个。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.TakeRange
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 buffer = source.TryGetNonEnumeratedCount(out int count) ? new List(capacity: count) : new List(); foreach (T item in source) { buffer.Add(item); }
TryGetNonEnumeratedCount检查实现ICollection/或利用Linq 使用的一些内部优化的源。ICollection
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
MaxBy和MinBy方法允许使用键选择器查找最大或最小元素,如下例所示。
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 chunks = Enumerable.Range(0, 10).Chunk(size: 3); // { {0,1,2}, {3,4,5}, {6,7,8}, {9} }
归功于罗伯特·安德森。
System.Linq的- FirstOrDefault/ LastOrDefault/SingleOrDefault过载采取默认参数
现有FirstOrDefault/ LastOrDefault/SingleOrDefault方法返回如果源枚举是空的。添加了新的重载,接受在这种情况下要返回的默认参数,如下面的示例所示。default(T)
Enumerable.Empty().SingleOrDefault(-1); // returns -1
感谢@ Foxtrek64。
System.Linq —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)) { }
感谢霍耀元。
优先队列
PriorityQueue
以下示例演示了.PriorityQueue
// creates a priority queue of strings with integer priorities var pq = new PriorityQueue(); // 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.
更快地将结构体作为字典值处理
CollectionsMarshal.GetValueRef是一个新的不安全API,它可以更快地更新字典中的结构值。新 API 旨在用于高性能场景,而不是用于一般用途。它返回refstruct 值,然后可以使用典型技术就地更新。
以下示例演示了如何使用新 API:
ref MyStruct value = CollectionsMarshal.GetValueRef(dictionary, key); // Returns Unsafe.NullRef() 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 倍性能回归。
利用 5 分钟的 Windows 闰秒数据滑动缓存,而不是在每次调用时获取。
支持所有平台上的 Windows 和 IANA 时区
这种改进有以下好处:
使用时的隐式转换(https://github.com/dotnet/runtime/pull/49412)TimeZoneInfo.FindSystemTimeZoneById
通过新的API显式转换上TimeZoneInfo:TryConvertIanaIdToWindowsId,TryConvertWindowsIdToIanaId,和HasIanaId(https://github.com/dotnet/runtime/issues/49407)
改进了使用不同时区类型的系统之间的跨平台支持和互操作。
删除需要使用 TimeZoneConverter OSS 库。该功能现已内置。
改进的时区显示名称
从 返回的列表中的显示名称中消除歧义。TimeZoneInfo.GetSystemTimeZones
利用 ICU / CLDR 全球化数据。
仅适用于 Unix。Windows 仍然使用注册表数据。这可能会在以后更改。
还进行了以下额外改进:
UTC 时区的显示名称和标准名称被硬编码为英语,现在使用与其余时区数据相同的语言(CurrentUICulture在 Unix 上,Windows 上的操作系统默认语言)。
由于大小限制,Wasm 中的时区显示名称改为使用非本地化的 IANA ID。
TimeZoneInfo.AdjustmentRule嵌套类将其BaseUtcOffsetDelta内部属性公开并获得一个新的构造函数baseUtcOffsetDelta作为参数。(https://github.com/dotnet/runtime/issues/50256)
TimeZoneInfo.AdjustmentRule 还获得了在 Unix 上加载时区的其他修复(https://github.com/dotnet/runtime/pull/49733),(https://github.com/dotnet/runtime/pull/50131)
改进了对 Windows ACL 的支持
System.Threading.AccessControl现在包括对与 Windows 访问控制列表 (ACL) 交互的改进支持。为、和的OpenExisting和TryOpenExisting方法添加了新的重载。这些具有“安全权限”实例的重载允许打开使用特殊 Windows 安全属性创建的线程同步对象的现有实例。EventWaitHandleMutexSemaphore
此更新与 .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 一次性方法
该HMAC类现在有静态方法,允许HMACs的一次性计算不分配。这些添加类似于先前版本中添加的用于哈希生成的一次性方法。System.Security.Cryptography
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(); } }
它可用于创建高级系统,例如复杂的缓存系统或该类型的自定义版本。例如,MVVM 工具包中的类型将使用它来避免广播消息时的内存分配。ConditionalWeakTable
可移植线程池
在.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 -> 640K)。这是一个惊人的改进,无需更改代码。
我们的目标是在 .NET 的未来版本中默认启用 Dynamic PGO,希望与 .NET 7 一起使用。我们强烈建议您在您的应用程序中尝试使用 Dynamic PGO 并向我们提供反馈。
完整的 PGO
要获得动态 PGO 的全部优势,您可以设置两个额外的环境变量:和. 这确保了尽可能多的方法参与分层编译。我们称这种变体为Full PGO。与动态 PGO 相比,完整 PGO 可以提供更大的稳态性能优势,但启动时间会更慢(因为必须在第 0 层执行更多方法)。DOTNET_TC_QuickJitForLoops=1DOTNET_ReadyToRun=0
您不希望将此选项用于短期运行的无服务器应用程序,但对于长期运行的应用程序可能有意义。
在未来的版本中,我们计划精简和简化这些选项,以便您可以更简单地获得完整 PGO 的好处,并适用于更广泛的应用程序。
静态 PGO
我们目前使用静态 PGO来优化 .NET 库程序集,如随 R2R(准备运行)提供的程序集。System.Private.CoreLib
静态 PGO 的好处在于,当程序集使用 crossgen 编译为 R2R 格式时,会进行优化。这意味着在没有运行时成本的情况下有运行时优势。例如,这非常重要,这也是 PGO 对 C++ 很重要的原因。
循环对齐
内存对齐是现代计算中各种操作的常见要求。在 .NET 5 中,我们开始在 32 字节边界对齐方法。在 .NET 6 中,我们添加了一个功能来执行自适应循环对齐,该功能NOP在具有循环的方法中添加填充指令,以便循环代码从 mod(16) 或 mod(32) 内存地址开始。这些更改提高并稳定了 .NET 代码的性能。
在下面的冒泡排序图中,数据点 1 表示我们开始在 32 字节边界对齐方法的点。数据点 2 表示我们也开始对齐内部循环的点。如您所见,基准测试的性能和稳定性都有显着提高。
硬件加速结构
结构是 CLR 类型系统的重要组成部分。近年来,它们经常被用作整个 .NET 库中的性能原语。最近的例子是ValueTask,ValueTuple和。记录结构是一个新的例子。在 .NET 5 和 .NET 6 中,我们一直在提高结构的性能,部分是通过确保结构可以保存在超快的 CPU 寄存器中,当它们是局部变量、参数或方法的返回值时)。这对于使用向量计算的 API 尤其有益。Span
稳定性能测量
团队中有大量的工程系统工作从未出现在博客上。这适用于您使用的任何硬件或软件产品。JIT 团队开展了一个项目来稳定性能测量,目标是增加我们内部性能实验室自动化自动报告的回归值。这个项目很有趣,因为进行了深入的调查以及实现稳定性所需的产品更改。它还展示了我们衡量保持和提高绩效的规模。
此图像展示了不稳定的性能测量,其中性能在连续运行中在慢速和快速之间波动。x 轴是测试日期,y 轴是测试时间(以纳秒为单位)。在图表的末尾(在提交这些更改之后),您可以看到测量值稳定下来,结果最好。此图像演示了单个测试。还有更多测试在dotnet/runtime #43227中被证明具有类似的行为。
准备运行的代码 / Crossgen 2
Crossgen2 是crossgen 工具的替代品。它旨在满足两个结果:
使跨代开发更高效。
启用一组当前无法通过 crossgen 实现的功能。
这种转换有点类似于本机代码 csc.exe 到托管代码Roslyn 编译器。Crossgen2 是用 C# 编写的,但是,它没有像 Roslyn 那样公开花哨的 API。
我们可能已经/已经为 .NET 6 和 7 计划了六个依赖于 crossgen2 的项目。矢量指令默认值的提议是我们想要为 .NET 6 进行的 crossgen2 功能和产品更改的一个很好的例子,但更有可能是 .NET 7。版本气泡是另一个很好的例子。
Crossgen2 支持跨操作系统和架构维度的交叉编译(因此得名“crossgen”)。这意味着您将能够使用单个构建机器为所有目标生成本机代码,至少与准备运行的代码相关。然而,运行和测试该代码是另一回事,为此您需要适当的硬件和操作系统。
第一步是用crossgen2编译平台本身。我们使用 .NET 6 完成了所有架构。因此,我们能够在此版本中淘汰旧的 crossgen。请注意,crossgen2 仅适用于 CoreCLR,不适用于基于 Mono 的应用程序(它们具有一组单独的代码生成工具)。
这个项目——至少在开始时——并不以性能为导向。目标是为托管 RyuJIT(或任何其他)编译器提供更好的架构,以离线方式(不需要或启动运行时)生成代码。
您可能会说“嘿……如果 crossgen2 是用 C# 编写的,那么您不必启动运行时来运行它吗?” 是的,但这不是本文中“离线”的意思。当 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,然后我们将回到对“crossgen2”使用“crossgen”这个术语。
.NET 诊断:EventPipe
EventPipe 是我们的跨平台机制,用于在进程内或进程外输出事件、性能数据和计数器。从 .NET 6 开始,我们已将实现从 C++ 移至 C。通过此更改,Mono 也使用 EventPipe。这意味着 CoreCLR 和 Mono 使用相同的事件基础结构,包括 .NET 诊断 CLI 工具。
这一变化还伴随着 CoreCLR 的小幅缩小:
我们还进行了一些更改,以在负载下提高 EventPipe 吞吐量。在最初的几个预览版中,我们进行了一系列更改,使吞吐量提高了 .NET 5 所能达到的 2.06 倍:
对于此基准测试,越高越好。.NET 6 是橙色线,.NET 5 是蓝色线。
开发工具包
对 .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动词查询更新的工作负载清单、更新本地清单、下载已安装工作负载的新版本,然后删除工作负载的所有旧版本。这类似于(在基于 Debian 的 Linux 发行版上使用)。将工作负载视为 SDK 的私有包管理器是合理的。它是私有的,因为它仅可用于 SDK 组件。我们将来可能会重新考虑这一点。nuget.orgapt update && apt upgrade -y
这些dotnet workload命令在给定 SDK 的上下文中运行。假设您同时安装了 .NET 6 和 .NET 7。工作负载命令将为每个 SDK 提供不同的结果,因为工作负载会有所不同(至少相同工作负载的不同版本)。
请注意,dotnet workload install将工作负载从 NuGet.org 复制到您的 SDK 安装中,因此sudo如果 SDK 安装位置受到保护(意味着在管理员/根位置),则需要提升运行或使用。
内置SDK版本检查
为了更轻松地跟踪新版本的 SDK 和运行时何时可用,我们向 .NET 6 SDK 添加了一个新命令。
dotnet sdk check
它会告诉您是否有更新版本可用于您已安装的任何 .NET SDK、运行时或工作负载。您可以在下图中看到新体验。
dotnet new
您现在可以在http://NuGet.org中搜索带有.dotnet new --search
模板安装的其他改进包括支持开关以支持私有 NuGet 源的授权凭据。--interactive
安装 CLI 模板后,您可以通过和检查更新是否可用。--update-check--update-apply
NuGet 包验证
包验证工具使 NuGet 库开发人员能够验证他们的包是否一致且格式良好。
这包括:
验证跨版本没有重大更改。
验证包对于所有特定于运行时的实现是否具有相同的公共 API 集。
确定任何目标框架或运行时适用性差距。
此工具是 SDK 的一部分。使用它的最简单方法是在项目文件中设置一个新属性。
true
更多 Roslyn 分析仪
在 .NET 5 中,我们随 .NET SDK 提供了大约 250 个分析器。其中许多已经存在,但作为 NuGet 包在带外发布。我们为 .NET 6 添加了更多分析器。
默认情况下,大多数新分析器在信息级别启用。您可以通过启用这些分析仪在警告级别配置的分析模式是这样的:。
我们发布了我们想要的 .NET 6 分析器集(加上一些额外的东西),然后将其中的大部分都准备好了。社区添加了几个实现,包括这些。
为平台兼容性分析器启用自定义防护
该CA1416平台兼容性分析仪已经可以识别使用的方法平台警卫OperatingSystem和RuntimeInformation,如和。但是,分析器不识别任何其他保护可能性,例如缓存在字段或属性中的平台检查结果,或者在辅助方法中定义了复杂的平台检查逻辑。OperatingSystem.IsWindowsOperatingSystem.IsWindowsVersionAtLeast
为了允许自定义保护的可能性,我们添加了新属性SupportedOSPlatformGuard并UnsupportedOSPlatformGuard使用相应的平台名称和/或版本注释自定义保护成员。该注释被平台兼容性分析器的流分析逻辑识别和尊重。
用法
[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 开发人员。