本文由葡萄城技术团队翻译并首发
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。
我们很高兴今天.NET5.0正式发布。这是一个重要的版本—其中也包括了C# 9和F# 5大量新特性和优秀的改进。微软和其他公司的团队已经在生产和性能测试环境中开始使用了。这些团队向我们反馈的结果比较令人满意,它证明了对性能提升及降低Web应用托管成本的机会有积极的表现。从预览版1开始,我们一直在5.0上运行我们自己的网站。从我们目前的所见所闻来看,.NET5.0无需在升级上花费太多的精力就能带来巨大的价值。对于你的下一个应用来说,这是一个很好的选择,而且可以直接从早期的.NET Core版本升级。我们希望您在台式机、笔记本电脑和云实例上正式开始使用它。
ASP.NET Core、EF Core、C#9和F#5也将在今天一同发布
您可以下载.NET5.0,适用于Windows、MacOS和Linux,适用于x86、x64、Arm32和Arm64。
- l 安装程序和二进制文件
- l Docker 容器 images
- l Linux package
- l 发行说明文档
- l 已知的问题
- l GitHub问题跟踪器
- l .NET 5.0贡献者
对于Visual Studio用户,您需要Visual Studio 16.8或更高的版本才能在Windows上使用.NET 5.0,在MacOS上使用最新版本的Visual Studio for Mac)。Visual Studio Code的C#扩展也已经支持.NET5.0和C#9。
NET 5.0是我们的.NET统一之旅的第一个版本。我们构建.NET 5.0是为了让更多的开发人员能够将他们的.NET Framework代码和应用程序迁移到.NET5.0。我们在5.0中也做了很多前期工作,以便Xamarin开发人员在发布.NET6.0时可以使用统一的.NET平台。在后面的文章中会有更多关于.NET统一的内容。
这个版本是完全开源的第五个主要的.NET版本。现在,在GitHub上的DotNet org中,有大量的个人和公司(包括.NET Foundation企业赞助商)作为一个大型社区在.NET的各个方面共同工作,.NET5.0中的改进是许多人通过他们的努力及创新的想法构成的结果,所有这些都超出了微软对该项目的管理。为此,我们向所有为.NET 5.0(以及之前的版本)做出贡献的人表示 “万分感谢”!
我们早在2019年5月就引入了.NET5.0,当时甚至设定了2020年11月的发布日期,结果我们如期发布了,为此要感谢团队中的每一个人,是他们让这一切成为现实!
2021年11月我们还将发布.NET
6.0,今后每年的11月我们都将发布新的.NET版本。
.NET 5.0亮点
在.NET5.0中有许多重要的改进:
l .NET5.0已经在dot.net和Bing.com上托管了几个月,已经经过了数个月的实际测试。
l 许多组件的性能都得到了极大的提高,在.NET5.0中的性能改进、.NET5.0中的ARM64性能和GRPC中都有详细描述。
l C#9和F#5提供了新的语言改进,比如C# 9的顶级程序和记录,而F# 5提供了交互式编程,.NET上函数式编程的性能得到了提升。
l .NET库增强了Json序列化、正则表达式和HTTP(HTTP 1.1、HTTP/2)的性能。
l 改进了GC、分层编译和其他方面,P95延迟有所降低。
l 通过ClickOnce客户端发布应用程序,单文件应用程序,减小的容器映像大小以及添加的Server Core容器映像,应用程序部署选项更好。
l Windows Arm64和WebAssembly扩展了平台范围。
我已经为.NET5.0写了很多Demo。您可以看一下这些.NET5.0示例,以了解更多关于新的C#9和库特性的信息。
平台和Microsoft支持
对于Windows、MacOS和Linux,.Net 5.0的平台支持列表与.NET Core 3.1几乎相同。如果您在受支持的操作系统上使用.NET Core 3.1,则应该能够在该操作系统的大部分版本上采用.NET 5.0。在.NET 5.0中,最重要的新增功能是Windows Arm64。
.Net 5.0是当前版本。这意味着它将在.NET6.0发布后的三个月内得到支持。因此,我们预计到2022年2月中旬将支持.NET5.0。与.NET
Core 3.1一样,.NET6.0将是一个LTS版本,并将支持三年。
平台统一的愿景
去年,我们分享了一个统一的.NET生态系统的愿景。它对您的价值在于,您将能够使用同一组API、语言和工具来覆盖广泛的应用类型,其中包括移动、云、桌面和物联网。您可能已经意识到,现在您已经可以使用.NET面向广泛的平台,但可能工具和API在Web和Mobile之间并不总是相同的,或者并不总是同时发布的。
作为.NET5.0和6.0的一部分,我们正在将.NET的产品体验尽可能的进行统一,同时使您能够选择您想要使用的.NET平台的部分。如果你想以Mobile而不是WebAssembly为目标,你不需要下载WebAssembly工具,反之亦然。ASP.NET Core和WPF也是如此。您还可以通过更简单的方式从命令行获取所需的所有.NET工具以及构建和运行时包。我们正在为.NET平台组件提供包管理器体验(包括使用现有的包管理器)。这对于很多场景来说都是很棒的。快速构建开发环境和CI / CD可能是最大的受益者。
实现这一愿景的第一步是整合.NET repos,包括Mono的一个大子集。拥有一个用于运行时和.NET库的repo是在任何地方交付相同产品的前提条件。它还有助于进行广泛的更改,这些更改会影响运行时和库,而这些库以前是有repo边界的。一些人担心,大规模回购将更难管理。事实证明并非如此。
在.NET 5.0版本中,Blazor是利用回购整合和.NET统一的最佳例子。Blazor WebAssembly的运行时和库现在是从合并的 DotNet/runtime repo所构建的。例如,这意味着服务器上的Blazor WebAssembly和Blazor对List<T> 将使用完全相同的代码。但在.NET5.0之前,Blazor并非如此。我们对Blazor WebAssembly采取的方法与我们在.NET6.0中使用Xamarin的方法非常相似。
.NET Framework仍然是受支持的Microsoft产品,并且每个新版本的Windows都将继续支持它。我们去年宣布已经停止向.NET Framework添加新功能,并完成了向.NET Core添加.NET Framework API。这意味着现在是考虑将您的.NET Framework应用程序迁移到.NET Core的好机会。对于.NET Framework客户端开发人员,.NET5.0支持Windows窗体和WPF。我们从许多开发人员那里听说,从.NET Framework移植非常简单。对于.NET Framework服务器开发人员,您需要采用ASP.NET Core才能使用.NET 5.0。对于Web Forms开发人员,我们相信Blazor通过更高效、更现代化的实现提供了类似的开发体验。WCF服务器和工作流用户可以查看支持这些框架的社区项目。从.NET Framework移植到.NET Core文档是一个很好的起点。这就是说,如果你对自己的体验满意,那么让你的应用程序运行在.NET Framework上是一个很好的方法。
Windows团队正致力于研究Reunion作为UWP及其相关技术的下一步。我们一直在与Reunion团队合作,以确保.NET5.0及更高版本能够很好地与WinUI和WebView2协同工作。
让我们来看看5.0版本中的新特性。
语言
C#9和F#5是.NET5.0版本的一部分,包含在.NET5.0 SDK中。Visual Basic也包含在5.0 SDK中。它不包括语言更改,但进行了改进以支持.NET Core上的Visual Basic应用程序框架。
C#源代码生成器是一项重要的C#编译器新特性。从技术上讲,它们不是C#9的一部分,因为它没有任何语言语法。请参阅新的C#源代码生成器示例,帮助您开始使用这一新功能。我们希望在.NET6.0及更高版本的.NET产品中更多地使用源代码生成器。
为了亲身试用新版本,我们部分人决定更新DotNet/iot
Repo,以使用新的C#9语法并以.NET5.0尝试目标。它使用顶级程序、记录、模式和切换表达式。它也已更新,以利用.NET库中完整的可为空的注释集。我们还更新了.NET物联网的文档。我们将查看该repo中的几个示例来探索C#9。
LED-BLINK程序是一个不错的紧凑高级程序示例
using System; using System.Device.Gpio; using System.Threading; var pin = 18; var lightTime = 1000; var dimTime = 200; Console.WriteLine($"Let's blink an LED!"); using GpioController controller = new (); controller.OpenPin(pin, PinMode.Output); Console.WriteLine($"GPIO pin enabled for use: {pin}"); // turn LED on and off while (true) { Console.WriteLine($"Light for {lightTime}ms"); controller.Write(pin, PinValue.High); Thread.Sleep(lightTime); Console.WriteLine($"Dim for {dimTime}ms"); controller.Write(pin, PinValue.Low); Thread.Sleep(dimTime); }
您可以看到target-typed的使用以及new对controller变量的分配。。GpioController类型仅在赋值的左侧定义。类型是在右手边推断出来的。这种新语法是var的另一种选择,var的类型只显示在赋值的右侧,并通过关键字var在左侧推断。
通过定义方法并利用在相同或其他文件中定义的类型,顶级程序也可能增加复杂性。CharacterLcd示例演示了其中一些功能。
逻辑和属性模式
C# 9包括对新模型的支持。您可以在如下代码中看到关于这个逻辑模式的示例。
var threshChoice = Console.ReadKey(); Console.WriteLine(); if (threshChoice.KeyChar is 'Y' or 'y') { TestThresholdAndInterrupt(ccs811); }
另一种新模式是属性模式。您可以在我的Mycroft信息访问6.0示例中看到几个属性检查。以下代码摘自PN532 RFID和NFC读取器示例。
if (pollingType is not { Length: <=15 }) { return null; }
此代码测试pollingType(类型为byte[]?)。为空或包含>15个字节。这是返回NULL之前需要测试的两个错误条件。也可以将此测试编写为pollingType为空或{Length:>15}。
我想再给你看两个模型。第一个是Mcp25xxx
CAN总线的逻辑模式。
public static byte GetRxBufferNumber(Address address) => address switch { >= Address.RxB0D0 and <= Address.RxB0D7 => 0, >= Address.RxB1D0 and <= Address.RxB1D7 => 1, _ => throw new ArgumentException(nameof(address), $"Invalid address value {address}."), };
第二个是Piezo蜂鸣器控制器中的逻辑模式。
if (element is not NoteElement noteElement) { // In case it's a pause element we have only just wait desired time. Thread.Sleep(durationInMilliseconds); } else { // In case it's a note element we play it. var frequency = GetFrequency(noteElement.Note, noteElement.Octave); _buzzer.PlayTone(frequency, (int)(durationInMilliseconds * 0.7)); Thread.Sleep((int)(durationInMilliseconds * 0.3)); }
记录
C#9包括一个名为Record的新类。与常规类相比,它有许多优点,其中一半与更简洁的语法有关。以下记录取自Bh1745 RGB传感器绑定。
public record ChannelCompensationMultipliers(double Red, double Green, double Blue, double Clear);
然后在同一文件中稍晚一点使用它,语法很熟悉:
ChannelCompensationMultipliers = new (2.2, 1.0, 1.8, 10.0);
可为空性注释的改进
现在,.NET库完全为空性添加了注释。这意味着如果您启用nullability,您将从平台获得更多类型信息来指导您使用该功能。目前,还没有对.NET文档进行完整的注释。例如,String.IsNullOrEmpty(String)应该被注释为接受一个字符串?,而String.Split(Char[])的注释是char[]?我们希望这个问题很快就能解决。完整的信息可以在Soure.dot.net上找到,也可以通过Visual Studio中的F12元数据查找获得。
System.Device.Gpio和Iot.Device.Bindings包(这两个包的版本都是1.1.0)也作为此版本的一部分进行了注释,使用了更新的.NET5.0注释。这两个库都是多目标的,但是,我们使用5.0视图为所有目标生成注释。
我们还添加了新的注释类型。大型类在从构造函数调用的帮助器方法中实例化对象成员是很常见的。C#编译器不能遵循对对象赋值的调用流程。当退出构造函数时,它会认为该成员为空,并将使用CS8618发出警告。MemberNotNull属性可以解决此问题。将该属性应用于帮助器方法。然后,编译器将看到您设置了此值,并意识到该方法是从构造函数调用的。MemberNotNullWhen类似。
您可以使用以下代码在BMxx80温度传感器中看到MemberNotNull的示例。
[MemberNotNull(nameof(_calibrationData))] private void ReadCalibrationData() { switch (this) { case Bme280 _: _calibrationData = new Bme280CalibrationData(); _controlRegister = (byte)Bmx280Register.CTRL_MEAS; break; case Bmp280 _: _calibrationData = new Bmp280CalibrationData(); _controlRegister = (byte)Bmx280Register.CTRL_MEAS; break; case Bme680 _: _calibrationData = new Bme680CalibrationData(); _controlRegister = (byte)Bme680Register.CTRL_MEAS; break; default: throw new Exception("Bmxx80 device not correctly configured. Could not find calibraton data."); } _calibrationData.ReadFromDevice(this); }
实际代码使用条件编译。这是因为该项目是多目标的,而该属性仅在.NET5.0+中受支持。使用该属性可以跳过运行时检查(在构造函数中),否则将需要这些检查来满足可空性要求,就像早期的.NET版本一样。
工具类
我们改进了Windows Forms 设计器,使其能在.NET5.0及更高版本中运行,更改了支持WinRT的方式,并进行了其他改进。
Windows窗体设计器
Windows窗体设计器(用于.NET Core 3.1和.NET5.0)已在Visual Studio 16.8中进行了更新,现在支持所有Windows窗体控件。设计器包括您指导的所有设计器功能,包括:拖放、选择、移动和调整大小、控件的剪切/复制/粘贴/删除、与属性窗口的集成、事件生成等。数据绑定和对更广泛的第三方控件集的支持很快就会到来。
.NET 5.0目标框架
在.NET5.0中,我们更改了用于目标框架的方法。以下项目文件演示了新的.NET5.0目标框架。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> </Project>
新的net5.0表单比我们之前使用的样式更紧凑、更直观。此外,我们正在扩展目标框架以描述操作系统依赖关系。我们希望通过.NET 6.0中的Xamarin定位iOS和Android,从而推动了这一变化。
net5.0netcoreapp3.1
Windows桌面API(包括Windows窗体、WPF和WinRT)仅在面向net5.0-windows时可用。您可以指定操作系统版本,如net5.0-Windows7或net5.0-windows10.0.17763.0(适用于Windows 2018年10月更新)。如果您想要使用WinRT API,则需要瞄准Windows 10版本。
当使用新的net5.0-windows tfm时,跨平台的场景可能会更具挑战性。例如,System.Device.Gpio演示了一种用于管理Windows目标框架的模式,例如,如果您希望避免为Windows构建或避免在Linux上拉取Windows运行时包。
更新摘要:
- l Net5.0是.NET5.0的新目标框架Moniker (TFM)。
- l Net5.0结合并取代了netcoreapp和netStandard tfms。
- l Net5.0支持.NET Framework兼容模式。
- l Net5.0-Windows将用于公开特定于Windows的功能,包括Windows Forms、WPF和WinRT API。
- l 特定于操作系统的TFMS可以包括操作系统版本号,如net6.0-ios14。
- l 像ASP.NET Core这样的可移植API将可以在net5.0上使用。同样的情况也适用于Net6.0的Xamarin Forms。
Visual Studio 16.8中的模板仍然以.NET Core 3.1为目标,用于控制台、WPF和Windows窗体应用程序。ASP.NET模板已更新为支持.NET5.0。我们将在Visual Studio 16.9中更新其余模板的模板。
WinRT Interop(重大更改)
关于以Windows API为目标的主题,我们已经转向了一个新的模型,将WinRT API作为.NET5.0的一部分来支持。这包括调用API(双向;CLR<==>WinRT),两个类型系统之间的数据封送处理,以及要在类型系统或ABI边界上被同等对待的类型的统一(即“投影类型”;IEnumerable和IIterable就是例子)。
现有的WinRT互操作系统已作为.NET5.0的一部分从.NET运行时中移除。这是一个突破性的变化。这意味着使用WinRT和.NET Core 3.x的应用程序和库需要重新构建,不能按原样在.NET5.0上运行。使用WinRT API的库需要多目标来管理.NET Core 3.1和.NET5.0之间的这种差异。
展望未来,我们将依靠WinRT团队在Windows中提供的新CsWinRT工具。它生成基于C#的WinRT互操作程序集,这些程序集可以通过NuGet交付。这正是Windows团队正在为Windows中的WinRT API所做的事情。任何想要使用WinRT(在Windows上)作为互操作系统的人都可以使用该工具,以将本机API公开给.NET或将.NETAPI公开给本机代码。
CsWinRT工具在逻辑上类似于tlbimp和tlbexp,但要好得多。TLB工具依赖于.NET运行时中的大量COM互操作管道。CsWinRT工具只依赖于公共的.NETAPI。也就是说,C#9中的函数指针功能--在.NET5.0运行时中部分实现了--在一定程度上是受到CsWinRT工具需求的启发。
这种新的WinRT互操作模型有几个好处:
- l 它可以独立于.NET运行时进行开发和改进。
- l 它与为iOS和Android等其他操作系统提供的基于工具的互操作系统是对称的。
- l 该工具可以利用其他.NET特性(AOT、C#特性、IL链接),而这在以前的系统中不是一个选项。
- l 简化了.NET运行时代码库。
使用WinRT API不需要添加NuGet引用。以Windows10TFM为目标--刚才在.NET5.0TFM一节中已经讨论过了--已经足够了。如果您的目标是.NET Core 3.1或更早版本,则需要引用WinRT包。您可以在System.Device.Gpio项目中看到此模式。
原生导出
很长一段时间以来,我们一直要求为调用.NET代码的本机二进制文件启用导出。该场景的构建块是托管对UnManagedCeller sOnlyAttribute的API支持。
此功能是创建更高级别体验的构建块。我们团队中的Aaron
Robinson一直致力于一个.NET Native Exports项目,该项目为将.NET组件发布为本机库提供了更完整的体验。我们正在寻找有关此功能的反馈,以帮助决定是否应将该方法包含在产品中。
.NET原生导出项目使您能够:
- l 公开自定义本机导出。
- l 不需要像COM这样的更高级别的互操作技术。
- l 跨平台工作。
- 有一些现有的项目支持类似的场景,例如:
- l 不受管理的出口。
- l DllExport
多年来,我们在本机应用程序中看到了各种.NET托管模型。@rseanHall为此提出并实现了一种新颖的新模型,该模型利用了.NET应用程序托管层提供的所有内置应用程序功能(特别是加载依赖项),同时允许从本机代码调用自定义入口点。这在很多情况下都是完美的,可以想象在从本机应用程序托管.NET组件的开发人员中变得流行起来。这在以前是不存在的。谢谢你的贡献,@rseanHall。
两个主要PR:
- l 启用从应用上下文调用Get_Runtime_Delegate。
- l 实现HDT_GET_Function_POINTER
事件管道
事件管道是我们在.NET Core 2.2中添加的一个新的子系统和API,它可以在任何操作系统上执行性能和其他诊断调查。在.NET5.0中,事件管道已得到扩展,使探查器能够编写事件管道事件。此场景对于检测以前依赖ETW(在Windows上)监视应用程序行为和性能的探查器至关重要。
现在可以通过事件管道获得程序集加载信息。这一改进是使类似的诊断功能(例如Fusion Log Viewer)成为.NET Framework的一部分的开始。现在,您可以使用以下命令,使用Dotnet-TRACE来收集此信息:
dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4:4 -- ./MyApp –my-arg 1
该工作流程在DotNet-TRACE文档中进行了说明。您可以看到简单测试应用程序的程序集加载信息。
Microsoft.Extensions.Logging
我们对Microsoft.Extensions.Logging库中的控制台日志提供程序进行了改进。现在,您可以实现自定义ConsoleForMatter来完全控制控制台输出的格式化和彩色化。格式化程序API通过实现VT-100(受大多数现代终端支持)转义序列的子集来实现丰富的格式化。控制台记录器可以解析出不支持的终端上的转义序列,允许您为所有终端编写一个格式化程序。
除了对定制格式化程序的支持之外,我们还添加了一个内置的JSON格式化程序,它可以将结构化的JSON日志发送到控制台。
转储调试
调试托管代码需要了解托管对象和构造。数据访问组件(DAC)是运行时执行引擎的子集,它了解这些构造,可以在没有运行时的情况下访问这些托管对象。在Linux上收集的.Net Core进程转储现在可以在Windows上使用WinDBG或DotNet Dump Analyze进行分析。
我们还添加了对从MacOS上运行的.NET进程捕获ELF转储的支持。由于ELF不是MacOS上的本机可执行文件格式(像lldb这样的本机调试器不能处理这些转储),我们将其作为一种选择加入的特性。要在MacOS上启用转储收集支持,请设置环境变量COMPLUS_DbgEnableElfDumpOnMacOS=1。生成的转储可以使用DotNet Dump Analyze进行分析。
打印环境信息
随着.NET扩展了对新操作系统和芯片体系结构的支持,人们有时想要一种打印环境信息的方式。我们创建了一个简单的.NET工具来完成此任务,名为dotnet-runtimeinfo。
您可以使用以下命令安装和运行该工具。
dotnet tool install -f dotnet-runtimeinfo dotnet-runtimeinfo
该工具为您的环境生成以下形式的输出。
**.NET information Version: 5.0.0 FrameworkDescription: .NET 5.0.0 Libraries version: 5.0.0 Libraries hash: cf258a14b70ad9069470a108f13765e0e5988f51 **Environment information OSDescription: Linux 5.8.6-1-MANJARO-ARM #1 SMP Thu Sep 3 22:01:08 CEST 2020 OSVersion: Unix 5.8.6.1 OSArchitecture: Arm64 ProcessorCount: 6 **CGroup info** cfs_quota_us: -1 memory.limit_in_bytes: 9223372036854771712 memory.usage_in_bytes: 2740666368
运行时和库
在运行时和库中有很多改进
RyuJIT的代码质量改进
这个版本对JIT有很多改进,我在以前的.NET5.0预览帖中分享了其中的许多改进。在这篇文章中,我提升了来自社区的改变。
- l 将xmm用于堆栈prolog-dotnet/Runtime#32538-更改为x86/x64 prolog零位调整代码。改进:JSON;TechEmpower。致谢:本·亚当斯。
- l 用于ARM64的Vectorise位数组-Dotnet/Runtime#33749-BitArray类已更新,包括使用ARM64内部结构的ARM64硬件加速实现。BitArray的性能改进非常显著。感谢@Gnbrkm41。
- l 动态通用词典扩展特性dotnet/运行时#32270-一些(可能是大多数?)。基于改进了运行库用来存储有关泛型类型和方法的信息的低级(本机代码)字典的实现,泛型的使用现在有了更好的性能(最初的性能发现)。有关更多信息,请参见Perf:COLLECTION COUNT()在Core中比CLR慢。错误报告归功于@RealDotNetDave。
- l Implementate Vector.Celing/Vector.Floor Dotnet/Runtime#31993-按照API提案,使用x64和Arm64内部函数实现Vector.Celing/Vector.Floor。感谢@Gnbrkm41。
- l TailCall助手的新的、更快的、可移植的实现。来源:Jakob Botsch Nielsen(.NET团队实习生)。来自@dsymetweet的反应。
- l 添加VectorTableList和TableVectorExtension内部-Credit:@TamarChristinaArm(ARM Holdings)。
- l 使用新的硬件内部组件BSF/BSR-Credit@saucecontrol提高英特尔架构性能。
- l 实现向量{Size}.AllBitsSet-Credit@Gnbrkm41。
- l 允许逃避一些边界检查-Credit@Nathan-Moore
垃圾收集器GC
在GC中进行了以下改进。
- l 卡片标记窃取-dotnet/coreclr#25986-服务器GC(在不同线程上)现在可以工作窃取,同时标记由老一代对象持有的0/1类对象。这意味着,在某些GC线程标记时间比其他线程长得多的情况下,短暂的GC暂停会更短。
- l 引入固定对象堆-DotNet/Runtime#32283-添加固定对象堆(PoH)。这个新堆(大对象堆(LOH)的对等体)将允许GC单独管理固定对象,从而避免固定对象对世代堆的负面影响。
- l 允许从空闲列表分配大对象,同时后台使用空闲列表清理启用了SOH的LOH分配,而BGC正在清理SOH。以前,这只在LOH上使用段末尾空间。这允许更好地使用堆。
- l 后台GC挂起修复-dotnet/coreclr#27729-挂起修复减少了bgc和用户线程挂起的时间。这减少了在GC发生之前挂起托管线程所需的总时间。Dotnet/coreclr#27578也促成了同样的结果。
- l 修正了扩展坞中的命名组组处理,增加了对从命名组组读取限制的支持。以前我们只读全球版本。
- l 优化矢量化排序-Dotnet/Runtime#37159-GC中的矢量化标记列表排序,减少了短暂的GC暂停时间(也包括Dotnet/Runtime#40613)。
- l 世代感知分析-DotNet/Runtime#40322-世代感知分析,允许您确定哪些老一代对象保留在年轻一代对象上,从而使它们存活下来,并导致短暂的GC暂停时间。
- l 优化分解GC堆内存页面-Dotnet/Runtime#35896-优化分解,更好地分解逻辑,对于服务器GC来说,完全退出了“停止世界”阶段,从而减少了阻塞GC的暂停时间。
现在,GC通过EgGetGCMmemyInfo方法公开最新集合的详细信息。GCMmemyInfo结构提供有关机器内存、堆内存和您指定的GC类型的最新集合或最新集合的信息-临时GC、完全阻塞GC或后台GC。
使用这个新API的最有可能的用例是日志记录/监视,或者向加载器平衡器指示机器应该停止旋转以请求完整的GC。它还可以通过减小缓存大小来避免容器硬限制。
另一个小而有效的改变是将昂贵的重置内存操作推迟到内存不足的情况。我们预计策略中的这些更改将降低GC延迟(以及总体上的GC CPU使用率)。
Windows ARM64
现在,.NET应用程序可以在Windows
Arm64上本地运行。在此之前,我们在.NETCore3.0中添加了对Linux Arm64的支持(对Glibc和MUSL的支持)。使用.NET 5.0,您可以在Windows Arm64设备(如Surface
Pro X)上开发和运行应用程序。您已经可以在Windows Arm64上运行.NET Core和.NET Framework应用程序,但需要通过x86仿真。这是可行的,但是原生ARM64执行的性能要好得多。
针对ARM64的MSI安装程序是此版本的最终更改之一。您可以在下图中看到.NET5.0SDK安装程序。
NET 5.0 SDK目前不包含Windows Arm64上的Windows桌面组件-Windows Forms和WPF。这一变化最初是在.NET5.0预览版8中发布的。我们希望在5.0服务更新中添加Windows Arm64的Windows桌面包。我们目前还没有可以分享的日期。在此之前,Windows Arm64支持SDK、控制台和ASP.NET Core应用程序,但Windows桌面组件不支持。
ARM64性能
一年多来,我们在提高ARM64性能方面投入了大量资金。我们致力于使ARM64成为一个基于.NET的高性能平台。这些改进同样适用于Windows和Linux。平台可移植性和一致性一直是.NET令人信服的特点。这包括无论您在哪里使用.NET都能提供出色的性能。在.NET Core 3.x中,ARM64的功能与x64不相上下,但缺少一些关键的性能特性和投资。我们已经在.NET5.0中解决了这个问题,正如在.NET5.0中的ARM64性能中所描述的那样。
改进之处在于:
- l 调整ARM64的JIT优化(示例)。
- l 启用并利用ARM64硬件特性(示例)。
- l 调整ARM64库中的关键性能算法(例如)。
有关更多详细信息,请参见在.NET5.0中提高Arm64性能。
硬件内部属性是我们在.NET Core3.0中添加的一个低级性能特性。当时,我们增加了对x86-64指令和芯片的支持。作为.NET5.0的一部分,我们正在扩展该功能以支持ARM64。仅仅创建内部结构并不能提高性能。它们需要在性能关键型代码中使用。我们在.NET5.0的.NET库中广泛利用了Arm64的内部特性。您也可以在自己的代码中做到这一点,尽管您需要熟悉CPU指令才能做到这一点。
我将用一个类比来解释硬件内部是如何工作的。在很大程度上,开发人员依赖于.NET中内置的类型和API,比如string.Split或HttpClient。这些API通常通过P/Invoke功能利用本地操作系统API。P/Invoke支持高性能的本机互操作,并在.NET库中为此广泛使用。您可以自己使用相同的功能来调用本机API。硬件内部功能类似,不同之处在于它们不是调用操作系统API,而是使您能够在代码中直接使用CPU指令。它大致相当于C++内部函数的.NET版本。硬件本质最好被认为是一种CPU硬件加速功能。它们提供了非常实实在在的好处,现在是.NET库性能基础的关键部分,并负责您可以在.NET5.0性能帖子中读到的许多好处。与C++相比,当.NET内部函数被AOT编译成随时可以运行的文件时,内部函数没有运行时性能损失。
注意:Visual C++编译器具有类似的内部特性。您可以直接将C++与.NET硬件内部功能进行比较,如果您在System.Rune me.Intrinsics.X86.Avx2、x64(AMD64)内部功能列表和英特尔内部功能指南中搜索_mm_i32ather_ep32,就可以看到这一点。你会看到很多相似之处。
我们在5.0中对ARM64的性能进行了第一次重大投资,并将在后续版本中继续这方面的努力。我们直接与ARM Holdings的工程师合作,确定产品改进的优先顺序,并设计最充分利用ARMv8
ISA的算法。其中一些改进将为ARM32带来价值,然而,我们并没有将独特的努力应用于ARM32。如果你使用的是Raspberry Pi,如果你安装了新的Arm64版本的Raspberry Pi OS,你会享受到这些改进。
我们预计苹果将在任何时候发布新的苹果硅基Mac电脑。我们已经有了针对Apple Silicon的.NET6.0的早期版本,并一直在与苹果的工程师合作,帮助为该平台优化.NET。我们在Apple Silicon(Credit@Sickler)上也有一些早期的社区参与。
P95+延迟
我们看到越来越多的大型面向互联网的网站和服务托管在.NET上。虽然有很多合理的关注点放在每秒请求数(RPS)指标上,但我们发现没有大的网站所有者问我们这个问题或要求数百万的RPS。然而,我们听说了很多关于延迟的事情,特别是关于改善P95或P99延迟的问题。通常,为站点配置的机器或核心的数量(以及最大的成本驱动因素)是根据达到特定的P95指标(而不是P50)来选择的。我们认为延迟是真正的“金钱指标”。
我们在Stack Overflow的朋友们在分享他们服务上的数据方面做得很好。他们的一位工程师尼克·克雷弗(Nick Craver)最近分享了他们在迁移到.NET Core后看到的延迟改进:
锁定对象一直是GC性能的长期挑战,特别是因为它们加速(或导致)内存碎片。我们为固定对象添加了一个新的GC堆。固定对象堆基于这样的假设,即进程中固定的对象非常少,但它们的存在会造成不成比例的性能挑战。将固定对象(尤其是那些由.NET库作为实现细节创建的对象)移动到一个独特的区域是有意义的,这样会使世代GC堆中只有很少的固定对象,甚至没有固定对象,因此性能会大大提高。
最近,我们一直在应对大中华区长期存在的挑战。DotNet/Runtime#2795应用了一种新的GC静态扫描方法,在确定GC堆对象的活跃度时避免了锁争用。Dotnet/Runtime#25986使用了一种新算法,用于在垃圾收集的标记阶段跨核心平衡GC工作,这应该会增加大堆垃圾收集的吞吐量,进而减少延迟。
提高分层编译的性能
我们一直致力于改进多版本的分层编译。我们继续将其视为一个关键的性能特性,无论是启动性能还是稳态性能。在这个版本中,我们对分层编译做了两大改进。
分层编译的主要机制是调用计数。一旦一个方法被调用n次,运行库就会要求JIT以更高的质量重新编译该方法。从我们最早的性能分析中,我们知道呼叫计数机制太慢,但没有看到一个简单的方法来解决这个问题。作为.NET5.0的一部分,我们改进了分层JIT编译使用的调用计数机制,以平滑启动时的性能。在过去的版本中,我们看到了在进程生命周期的前10-15秒(主要是Web服务器)中出现的不可预测的性能报告。这个问题现在应该得到解决。
我们发现的另一个性能挑战是对带有循环的方法使用分层编译。根本问题是,您可以使用一个循环多次的冷方法(只调用一次或几次;$lt;n)。我们称这种病态场景为“冷方法;热循环”。很容易想象这种情况会发生在应用程序的Main方法中。因此,默认情况下,我们禁用了具有循环的方法的分层编译。相反,我们允许应用程序选择使用带循环的分层编译。PowerShell是在看到某些场景的高个位数性能改进后选择这样做的应用程序。
为了更好地处理具有循环的方法,我们实现了栈上替换(OSR)。这类似于Java虚拟机具有的同名功能。OSR使当前正在运行的方法执行的代码能够在方法执行过程中重新编译,而这些方法是活动的“堆栈上”。这一功能目前还处于试验阶段和选择加入阶段,并且仅在x64上。
要使用OSR,必须启用多项功能。PowerShell项目文件是一个很好的起点。您会注意到,分层编译和所有快速JIT功能都已启用。此外,需要将COMPLUS_TC_OnStackReplace环境变量设置为1。
或者,您也可以设置以下两个环境变量,假设所有其他设置都有其缺省值:
COMPLUS_TC_QuickJitForLoops=1。
COMPLUS_TC_OnStackReplace=1。
我们不打算在.NET5.0中默认启用OSR,也还没有决定是否会在生产中支持它。
在Windows上支持ICU
我们使用ICU库来支持Unicode和全球化,以前只在Linux和MacOS上使用。我们现在在Windows
10上使用相同的库。这一更改使得全球化API的行为在Windows
10、MacOS和Linux之间保持一致,例如特定于区域性的字符串比较。我们还将ICU与Blazor WebAssembly配合使用。
将System.DirectoryServices.Protooles扩展到Linux和MacOS
我们一直在添加对System.DirectoryServices.Protooles的跨平台支持。这包括对Linux的支持和对MacOS的支持。Windows支持是预先存在的。
协议是一个比System.DirectoryServices更低级的API,可以支持(或可以用来支持)更多场景。System.DirectoryServices包含仅限Windows的概念/实现,因此它不是跨平台的明显选择。这两个API集都支持控制目录服务服务器并与其交互,如LDAP或Active Directory。
System.Text.Json
System.Text.Json在.NET5.0中得到了显著改进,以提高性能和可靠性,并使熟悉Newtonsoft.Json的人们更容易采用。它还支持将JSON对象反序列化为记录。
如果您正在考虑使用System.Text.Json作为Newtonsoft.Json的替代方案,您应该查看迁移指南。本指南阐明了这两个API之间的关系。Json旨在涵盖许多与Newtonsoft.Json相同的场景,但它并不是要替代流行的JSON库,也不是要实现与流行的JSON库相同的功能。我们试图在性能和可用性之间保持平衡,在我们的设计选择中偏向于性能。
HttpClient扩展方法
JsonSerializer扩展方法现在在HttpClient上公开,极大地简化了这两个API的结合使用。这些扩展方法消除了复杂性,并为您处理了各种场景,包括处理内容流和验证内容媒体类型。Steve Gordon很好地解释了在System.Net.Http.Json中使用HttpClient发送和接收JSON的好处。
下面的示例将天气预报JSON数据反序列化为Forecast记录,使用新的
using System; using System.Net.Http; using System.Net.Http.Json; string serviceURL = "https://localhost:5001/WeatherForecast"; HttpClient client = new(); Forecast[] forecasts = await client.GetFromJsonAsync<Forecast[]>(serviceURL); foreach(Forecast forecast in forecasts) { Console.WriteLine($"{forecast.Date}; {forecast.TemperatureC}C; {forecast.Summary}"); } // {"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"} public record Forecast(DateTime Date, int TemperatureC, int TemperatureF, string Summary);
这段代码很紧凑!它依赖于C#9中的顶级程序和记录以及新的GetFromJsonAsync<T>()扩展方法。在如此接近的情况下使用Foreach和Await可能会让您怀疑,我们是否要添加对JSON对象流的支持。我真的希望如此。
您可以在自己的机器上尝试此功能。以下.NET SDK命令将使用WebAPI模板创建天气预报服务。默认情况下,它将在以下网址公开服务:https://localhost:5001/WeatherForecast.。这与示例中使用的URL相同。
rich@thundera ~ % dotnet new webapi -o webapi rich@thundera ~ % cd webapi rich@thundera webapi % dotnet run
确保您已经运行了DotNet dev-certs https--首先信任,否则客户端和服务器之间的握手将不起作用。如果遇到问题,请参阅信任ASP.NET核心HTTPS开发证书。
然后,您可以运行上一个示例。
rich@thundera ~ % git clone https://gist.github.com/3b41d7496f2d8533b2d88896bd31e764.git weather-forecast rich@thundera ~ % cd weather-forecast rich@thundera weather-forecast % dotnet run 9/9/2020 12:09:19 PM; 24C; Chilly 9/10/2020 12:09:19 PM; 54C; Mild 9/11/2020 12:09:19 PM; -2C; Hot 9/12/2020 12:09:19 PM; 24C; Cool 9/13/2020 12:09:19 PM; 45C; Balmy
改进了对不可变类型的支持
定义不可变类型有多种模式。记录只是最新的记录。JsonSerializer现在支持不可变类型。
在本例中,您将看到带有不可变结构的序列化。
using System; using System.Text.Json; using System.Text.Json.Serialization; var json = "{"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"} "; var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true, IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var forecast = JsonSerializer.Deserialize<Forecast>(json, options); Console.WriteLine(forecast.Date); Console.WriteLine(forecast.TemperatureC); Console.WriteLine(forecast.TemperatureF); Console.WriteLine(forecast.Summary); var roundTrippedJson = JsonSerializer.Serialize<Forecast>(forecast, options); Console.WriteLine(roundTrippedJson); public struct Forecast{ public DateTime Date {get;} public int TemperatureC {get;} public int TemperatureF {get;} public string Summary {get;} [JsonConstructor] public Forecast(DateTime date, int temperatureC, int temperatureF, string summary) => (Date, TemperatureC, TemperatureF, Summary) = (date, temperatureC, temperatureF, summary); }
注意:JsonConstructor属性是指定要与结构一起使用的构造函数所必需的。对于类,如果只有一个构造函数,则不需要该属性。记录也是如此。
它会产生以下输出:
rich@thundera jsonserializerimmutabletypes % dotnet run 9/6/2020 11:31:01 AM -1 31 Scorching {"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"}
支持记录
JsonSerializer对记录的支持几乎与我刚才向您展示的对不可变类型的支持相同。我想在这里展示的不同之处在于将JSON对象反序列化为一个记录,该记录公开了一个参数化的构造函数和一个可选的init属性。
以下是程序片段,包括Record定义:
using System; using System.Text.Json; Forecast forecast = new(DateTime.Now, 40) { Summary = "Hot!" }; string forecastJson = JsonSerializer.Serialize<Forecast>(forecast); Console.WriteLine(forecastJson); Forecast? forecastObj = JsonSerializer.Deserialize<Forecast>(forecastJson); Console.Write(forecastObj); public record Forecast (DateTime Date, int TemperatureC) { public string? Summary {get; init;} };
它会产生以下输出:
rich@thundera jsonserializerrecords % dotnet run {"Date":"2020-09-12T18:24:47.053821-07:00","TemperatureC":40,"Summary":"Hot!"} Forecast { Date = 9/12/2020 6:24:47 PM, TemperatureC = 40, Summary = Hot! }
改进了对Dictionary<K,V>的支持
JsonSerializer现在支持使用非字符串键的字典。您可以在下面的示例中看到这是什么样子。在.NET Core 3.0中,此代码进行编译,但会引发NotSupportdException异常。
using System; using System.Collections.Generic; using System.Text.Json; Dictionary<int, string> numbers = new () { {0, "zero"}, {1, "one"}, {2, "two"}, {3, "three"}, {5, "five"}, {8, "eight"}, {13, "thirteen"}, {21, "twenty one"}, {34, "thirty four"}, {55, "fifty five"}, }; var json = JsonSerializer.Serialize<Dictionary<int, string>>(numbers); Console.WriteLine(json); var dictionary = JsonSerializer.Deserialize<Dictionary<int, string>>(json); Console.WriteLine(dictionary[55]);
它会产生以下输出。
rich@thundera jsondictionarykeys % dotnet run {"0":"zero","1":"one","2":"two","3":"three","5":"five","8":"eight","13":"thirteen","21":"twenty one","34":"thirty four","55":"fifty five"} fifty five
对字段的支持
JsonSerializer现在支持字段。这一变化是由@YohDeadfall贡献的。谢谢!。
您可以在下面的示例中看到这是什么样子。在.NET Core
3.0中,JsonSerializer无法序列化或反序列化使用字段的类型。对于具有字段且无法更改的现有类型,这是一个问题。有了这一变化,这就不再是问题了。
using System; using System.Text.Json; var json = "{"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"} "; var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true, IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var forecast = JsonSerializer.Deserialize<Forecast>(json, options); Console.WriteLine(forecast.Date); Console.WriteLine(forecast.TemperatureC); Console.WriteLine(forecast.TemperatureF); Console.WriteLine(forecast.Summary); var roundTrippedJson = JsonSerializer.Serialize<Forecast>(forecast, options); Console.WriteLine(roundTrippedJson); public class Forecast{ public DateTime Date; public int TemperatureC; public int TemperatureF; public string Summary; }
它会产生以下输出。
rich@thundera jsonserializerfields % dotnet run 9/6/2020 11:31:01 AM -1 31 Scorching {"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"}
保留JSON对象图中的引用
JsonSerializer增加了对在JSON对象图中保留(循环)引用的支持。它通过存储ID来实现这一点,当JSON字符串被反序列化为对象时,这些ID可以重新组成。
using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; Employee janeEmployee = new() { Name = "Jane Doe", YearsEmployed = 10 }; Employee johnEmployee = new() { Name = "John Smith" }; janeEmployee.Reports = new List<Employee> { johnEmployee }; johnEmployee.Manager = janeEmployee; JsonSerializerOptions options = new() { // NEW: globally ignore default values when writing null or default DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, // NEW: globally allow reading and writing numbers as JSON strings NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString, // NEW: globally support preserving object references when (de)serializing ReferenceHandler = ReferenceHandler.Preserve, IncludeFields = true, // NEW: globally include fields for (de)serialization WriteIndented = true,}; string serialized = JsonSerializer.Serialize(janeEmployee, options); Console.WriteLine($"Jane serialized: {serialized}"); Employee janeDeserialized = JsonSerializer.Deserialize<Employee>(serialized, options); Console.Write("Whether Jane's first report's manager is Jane: "); Console.WriteLine(janeDeserialized.Reports[0].Manager == janeDeserialized); public class Employee { // NEW: Allows use of non-public property accessor. // Can also be used to include fields "per-field", rather than globally with JsonSerializerOptions. [JsonInclude] public string Name { get; internal set; } public Employee Manager { get; set; } public List<Employee> Reports; public int YearsEmployed { get; set; } // NEW: Always include when (de)serializing regardless of global options [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public bool IsManager => Reports?.Count > 0; }
性能
在.NET5.0中,JsonSerializer的性能得到了显著提高。Stephen Toub在他的.NET5帖子中谈到了JsonSerializer的一些性能改进。我还在.NET5.0RC1文章中更详细地介绍了Json的性能。
应用程序部署
在编写或更新应用程序之后,您需要部署它以使您的用户受益。这可能是到Web服务器、云服务或客户端计算机,也可能是使用Azure DevOps或GitHub操作等服务的CI/CD流的结果。
我们努力提供一流的部署功能,自然地与应用程序类型保持一致。对于.NET5.0,我们专注于改进单文件应用程序,减少停靠多阶段构建的容器大小,并为使用.NET Core部署ClickOnce应用程序提供更好的支持。
容器
我们认为容器是最重要的云趋势,并在这方面投入了大量资金。我们正在以多种方式投资容器,在.NET软件堆栈的多个级别上。首先是我们对基本面的投资,这越来越多地受到容器场景和部署容器应用的开发者的影响。
我们正在让与集装箱管弦乐团的合作变得更容易。我们已经添加了OpenTelemeter支持,这样您就可以从您的应用程序中捕获分布式跟踪和指标。DotNet-monitor是一种新工具,旨在作为从.NET进程访问诊断信息的主要方式。特别是,我们已经开始构建dotnet-monitor的容器变体,您可以将其用作应用程序侧车。最后,我们正在构建DotNet/Tye,以此来提高微服务开发人员的工作效率,包括开发和部署到Kubernetes环境。
NET运行时现在支持cgroup v2,我们预计它将在2020年后成为与容器相关的重要API。Docker目前使用的是cgroup v1(已经被.NET支持)。相比之下,cgroup v2比cgroup v1更简单、更高效、更安全。您可以通过我们2019年Docker更新了解更多关于cgroup和Docker资源限制的信息。Linux发行版和容器运行时正在添加对cgroup v2的支持。一旦cgroup v2环境变得更加普遍,.Net 5.0将在cgroup v2环境中正常工作。这归功于Omair Majid,他在Red Hat支持.NET。
除了Nano Server,我们现在还发布Windows Server Core镜像。我们添加了Server Core,因为我们收到了客户的反馈,他们想要一个与Windows Server完全兼容的.NET映像。如果你需要这个,那么这张新照片就是为你准备的。支持Windows Server 2019长期服务渠道(LTSC)、.NET5.0和x64的组合。我们还进行了其他更改,以减小Windows服务器核心映像的大小。这些改进带来了很大的不同,但都是在Windows
Server 2019发布之后做出的。然而,它们将使下一个Windows Server LTSC版本受益。
作为使用“.NET”作为产品名称的一部分,我们现在将.NET Core 2.1、3.1和.NET5.0镜像发布到mcr.microsoft.com/dotnet系列的Repos中,而不是发布到mcr.microsoft.com/dotnet/core。我们将继续将.NET
Core 2.1和3.1双重发布到以前的位置,同时支持这些版本。.Net 5.0图像将仅发布到新位置。请相应地更新您的From语句和脚本。
作为.NET5.0的一部分,我们将SDK镜像重新建立在ASP.NET镜像之上,而不是构建包-dep,以显著减小您在多阶段构建场景中拉取的聚合镜像的大小。
此更改对于多阶段构建有以下好处,其中包含一个示例Dockerfile:
Ubuntu 20.04 Focus的多阶段构建成本:
Pull Image |
Before |
After |
sdk:5.0-focal |
268 MB |
232 MB |
aspnet:5.0-focal |
64 MB |
10 KB (manifest only) |
减少了约: 100 MB (-30%)
Debian 10 Buster的多阶段构建成本:
Pull Image |
Before |
After |
sdk:5.0 |
280 MB |
218 MB |
aspnet:5.0 |
84 MB |
4 KB (manifest only) |
减少了约: 146 MB (-40%)
有关更多详细信息,请参见Dotnet/Dotnet-docker#1814。
此更改有助于多阶段构建,其中SDK和您的目标aspnet或运行时镜像的版本相同(我们预计这是常见的情况)。在进行此更改时,(例如)aspnet拉入将是不可行的,因为您将通过最初的SDK拉入拉出aspnet层。
我们对阿尔卑斯和Nano服务器做了类似的更改。阿尔卑斯和Nano服务器都没有Buildpack-dep镜像。但是,阿尔卑斯和Nano Server的SDK镜像之前并不是在ASP.NET镜像之上构建的。我们解决了这个问题。你将会看到阿尔卑斯和Nano服务器以及5.0版本在多阶段构建方面都获得了巨大的成功。
单文件应用程序
单个文件应用程序作为单个文件发布和部署。该应用程序及其依赖项都包含在该文件中。当应用程序运行时,依赖项直接从该文件加载到内存中(不会影响性能)。
在.NET5.0中,单文件应用程序主要集中在Linux上(稍后会详细介绍)。它们可以是依赖于框架的,也可以是独立的。依赖于全球安装的.NET运行时,依赖于框架的单个文件应用程序可能非常小。自包含的单文件应用程序较大(由于带有运行库),但不需要在安装前安装.NET运行库,因此可以直接运行。一般来说,依赖于框架对开发和企业环境都有好处,而对于ISV来说,自包含通常是更好的选择。
我们用.NET Core 3.1制作了一个版本的单文件应用程序。它将二进制文件打包到单个文件中进行部署,然后将这些文件解压缩到一个临时目录中以加载和执行它们。在某些情况下,这种方法可能会更好,但我们希望我们为5.0构建的解决方案会更好,这是一个值得欢迎的改进。
要创建真正的单一文件解决方案,我们需要克服多个障碍。关键任务是创建一个更复杂的应用程序捆绑器,并教导运行库从二进制资源加载程序集。我们还遇到了一些无法逾越的障碍。
在所有平台上,我们都有一个称为“apphost”的组件。这是成为可执行文件的文件,例如Windows上的myapp.exe或基于Unix的平台上的./myapp。对于单一文件应用程序,我们创建了一个新的apphost,我们称之为“超级主机”。它具有与常规apphost相同的角色,但还包括运行时的静态链接副本。超级主机是我们单一文件方法的一个基本设计点。这个模型就是我们在带有.NET5.0的Linux上使用的模型。由于各种操作系统的限制,我们无法在Windows或MacOS上实现这种方法。我们在Windows或MacOS上没有超级主机。在这些操作系统上,本地运行时二进制文件(大约3个)位于单个文件应用程序旁边(导致“不是单个文件”)。我们将在.NET6.0中重新讨论这种情况,然而,我们预计我们遇到的问题仍然具有挑战性。
您可以使用以下命令来生成单文件应用程序。
l 依赖框架的单文件APP:
n DotNet PUBLISH-r Linux-x64--自含式FALSE/p:PublishSingleFile=TRUE。
l 自含式单文件APP:
n DotNet PUBLISH-r Linux-x64--自含式TRUE/p:PublishSingleFile=TRUE
您还可以使用项目文件配置单个文件发布。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <!-- The OS and CPU type you are targeting --> <RuntimeIdentifier>linux-x64</RuntimeIdentifier> <!-- Determine self-contained or framework-dependent --> <SelfContained>true</SelfContained> <!-- Enable single file --> <PublishSingleFile>true</PublishSingleFile> </PropertyGroup> </Project>
您可以尝试使用程序集修剪来减小应用程序的大小。它可能会通过过度修剪来破坏应用程序,因此建议在使用此功能后彻底测试您的应用程序。程序集调整还会移除提前编译的读到运行本机代码(用于被调整的程序集),这主要是为了提高性能。您需要在裁剪之后测试应用程序的性能。通过使用PublishReadyToRun属性(并设置为true),您可以在裁剪后即可运行编译您的应用程序。
注:
- l 应用程序是特定于操作系统和架构的。您需要发布每种配置(Linuxx64、LinuxArm64、Windowsx64、…)。。
- l 配置文件(如*.runtimeconfig.json)包含在单个文件中。如果需要,您可以在单个文件旁边放置额外的配置文件(可能是为了测试)。
- l 默认情况下,单个文件中不包括.pdb文件。您可以使用<DebugType>Embed</DebugType>属性启用PDB嵌入。
- l IncludeNativeLibrariesForSelfExtract属性可用于在Windows和MacOS上嵌入本机运行时二进制文件,但它们必须在运行时解压缩到临时存储中。不建议在一般情况下使用此功能。
ClickOnce
多年来,ClickOnce一直是流行的.NET部署选项。现在,它被.NET Core 3.1和.NET5.0
Windows应用程序支持。当我们将Windows窗体和WPF支持添加到.NET Core 3.0中时,我们知道很多人会希望使用ClickOnce进行应用程序部署。在过去的一年里,.NET和Visual Studio团队共同努力,在命令行和Visual Studio中启用ClickOnce发布。
从项目一开始,我们就有两个目标:
- l 在Visual Studio中为ClickOnce启用熟悉的体验。
- l 使用MSBuild或MAGE工具,通过命令行流为ClickOnce发布启用现代CI/CD。
- 用图片向你展示这种体验是最容易的。
让我们从Visual Studio体验开始,它以项目发布为中心。
我们目前支持的主要部署模式是依赖于框架的应用程序。很容易依赖于.NET桌面运行时(即包含WPF和Windows窗体的桌面运行时)。如果需要,ClickOnce安装程序将在用户计算机上安装.NET运行时。我们还打算支持独立和单一文件应用程序。
您可能会想,您是否仍然能够利用ClickOnce脱机和更新功能。可以,停那儿吧。
MAGE最大的变化是它现在是一个.NET工具,发布在NuGet上。这意味着你不需要在你的机器上安装任何特殊的东西。您只需要.NET5.0SDK,然后就可以将MAGE安装为一个.NET工具。您也可以使用它发布.NET Framework应用程序,但是,SHA1签名和部分信任支持已被移除。
MAGE安装命令如下:
dotnet tool install -g Microsoft.DotNet.Mage
在您制作并分发了ClickOnce安装程序之后,您的用户将看到熟悉的ClickOnce安装对话框。
当您使更新可用时,您的用户将看到更新对话框。
最后总结
Net 5.0是另一个大版本,它应该会改进你使用.NET的许多方面。我们已经实现了一系列的改进,从单文件应用程序到性能,从Json序列化的可用性到ARM64的支持。虽然今天可能是您使用.NET5.0的第一天,但我们在微软的产品中运行.NET5.0已经有几个月了。我们相信,它已准备好供您使用、运营您的业务并为您的应用程序提供动力。C#9和F#5中的新语言改进应该会使您的代码更具表现力,更易于编写。对于您现有的应用程序来说,.Net 5.0也是一个很好的选择。在许多情况下,您可以毫不费力地升级。
如果您对性能感兴趣,您可能会对我们在TechEmpower基准测试方面的进展感兴趣。回过头来看,您可以看到.NETCore3.1在最新一轮第19轮中的表现相当不错。我们期待着在即将到来的第20轮中看到.NET5.0。当第20轮最终确定并发布时,新的排名将是值得关注的。
在.NET5.0中的改进是许多人共同努力的结果,他们在GitHub上,在世界各地,在多个时区协同工作。感谢为这一版本做出贡献的每一个人。别担心,有很多机会可以贡献自己的力量。NET5.0版本已经结束,但是下一个版本已经开始了。