深度探秘.NET 5.0

今年11月10号 .NET 5.0 如约而至。这是.NET All in one后的第一个版本,虽然不是LTS(Long term support)版本,可是是生产环境可用的。linux

有着微软的背书:微软从.NET Preview 1就开始在本身的网站上运行.NET 5, (Bing.com、dot.net已升级并运行了数个月),同时早期的.NET Core版本能够直接升级到.NET 5.  因此你们是能够放心使用的。android

接下来,咱们深刻了解一下.NET 5.0此次带来了哪些新的特性。ios

1、.NET 5.0 的一些亮点(Highlights)git

1. 经过线上(生产环境)测试(battle-tested) : .NET5.0 经过在Bing.com和dot.net 托管运行数个月,全面经过了线上验证,这证实这个版本是生产可用的github

2. 性能大幅提高:GC、JIT、正则表达式、多线程和异步处理、集合、LINQ、网络访问、JSON序列化、gRPC等等,了解详细能够访问正则表达式

3. C# 9和F# 9 的语言提高:例如C#9的顶级程序和记录record,F#5提供了交互式编程,并提升了.NET的性能。算法

4. .NET库加强了Json序列化,正则表达式和HTTP(HTTP 1.1,HTTP / 2)的性能。这一点在第二条中已经有所涉及。docker

5. P95 的延迟有所减小,得益于GC、分层编译和其余组件的一些改进编程

6.更好、更灵活的应用部署选项:ClickOnce客户端应用程序发布,单文件应用程序,减少的容器映像大小以及添加的Server Core容器映像。json

7.平台支持的范围进一步扩展Windows Arm64WebAssembly

 2、再看统一平台的愿景

   2019年5月6号,微软发布了.NET 5.0 统一平台的愿景:未来只会有一个.NET,您将可使用它来定位Windows,Linux,macOS,iOS,Android,tvOS,watchOS和WebAssembly等。

   

   实现这一愿景的第一步是整合.NET仓库,即:整合关键的.NET代码库, 这是为.NET运行库和库提供一个存储库是在各处交付相同产品的前提。Blazor就是代码合并和.NET统一的最佳示例:Blazor WebAssembly的运行时和库如今是从合并的dotnet /运行时仓库中构建的。这意味着服务器上的Blazor WebAssembly和Blazor使用与彻底相同的代码List<T>。

   代码整合后,.NET Framework怎么办?

   .NET Framework仍然是受支持的Microsoft产品,而且每一个新版本的Windows都将继续支持.NET Framework。去年,微软宣布已中止向.NET Framework添加新功能,逐步向.NET Core添加更多的.NET Framework API。

   这就意味着,.NET Framework已经停更了,版本目前停留在.NET Framework 4.8. 

   这也是没办法的事情,统一后的.NET, 从.NET5.0开始迭代了。此次.NET 5.0的Release列表也能发现这个状况:

   

    在上述状况下,目前是将.NET Framework升级到.NET Core的最佳时机了。若是比较在乎LTS版本,也能够等到明年.NET 6统一升级。对于此,微软的建议是:

    对于.NET Framework客户端开发人员,.NET 5.0支持Windows窗体和WPF。

    对于.NET Framework服务器开发人员, 若是采用ASP.NET Core才能使用.NET 5.0。

    对于Web Forms开发人员,Blazor经过高效且更加现代的实现方式提供相似的开发人员体验。

    对于WCF服务器和Workflow用户能够查看支持这些框架的社区项目

    以上,对于统一后的.NET 5.0, 广大.NET Developers 能够放心、开心地去拥抱此次升级和统一,这表明了.NET的将来。

 3、深刻了解一下编程语言层面的提高(C# 9 和 F# 5)

   C#9和F#5是.NET 5.0版本的一部分,被包含在.NET 5.0 SDK中。接下来详细看一下C# 9 的一些语言新特性(F# 5用的比较少,再也不作详细介绍):

  1. Top-level programs 顶级程序

   你们会问这是什么?这是在顶级编写程序的一种更简单的方式:一个更简单的 Program.cs 文件。

   咱们知道,原先在Program类中,必须有Main函数,这是程序的一个EntryPoint入口。

using System;

namespace NET5Demo { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } } }

   .NET 5引入Top-level programs 后,咱们不须要写Main函数了。能够直接这么写:

System.Console.WriteLine("Hello World!");

 你们会有疑问,真的没有Main函数了吗?其实这是个语法糖,咱们经过IL Spy看一下反编译后的代码:

 

  2. 逻辑模式和属性模式匹配(Logical and property patterns)

  咱们可使用not or and 实现更强的更灵活的逻辑模式匹配:

  先看一个逻辑匹配的Demo:

var input = Console.ReadKey();
if (input.KeyChar is 'Y' or 'y') { Console.WriteLine("You choosed yes!"); }

  再看一个Switch的Demo:

int score = 90;
switch (score) { case 0: Console.WriteLine("0分."); break; case > 0 and <= 60: Console.WriteLine("合格."); break; case > 60 and <= 80: Console.WriteLine("优秀."); break; case > 80 and <= 100: Console.WriteLine("卓越."); break; }

 属性模式匹配:经过两个{},实现对对象属性的模式匹配。

Type type = Type.GetType("System.String");
if (type is not null and { FullName: "System.String" }) { Console.WriteLine("It's type is System.String."); }

  3. record类型

  record是一个新增的引用类型,与class很像,那么你们会问?为何增长一个record类型呢?它的使用场景是什么呢?

  答案:为了方便比较数据是否一致。咱们写个代码示意一下:

  假设咱们有个User类,包含ID、Name、Gender、Tel几个属性,若是咱们要对比2个User对象是否相等,咱们可能须要逐个属性对比,或重写Equals、GetHashCode方法。

  那么若是咱们用record类型呢?

record User(int Id, string Name, int Gender, string Tel);

 作个对象对比的Demo:

 
 

var userA = new User(1, "小米", 1, "123456789");
var userB = new User(1, "小米", 1, "123456789");

if (userA == userB)
{
    Console.WriteLine("这是一个用户."); }

 总结一下:record类型让开发省去了重写相等比较的业务逻辑,同时简化了类型定义和初始化

 4. 可空注解的增长和改进

  目前.NET library 类库,已经全面设置了是否可空注解。其实这个特性其实在C# 8.0已经引入:C#8.0 引入了“可为空引用类型”和“不可为空引用类型”,使你可以对引用类型变量的属性做出重要声明 :

  #nullable enable
  class A{ }

  即.NET 5.0的类库中已经全面更新了这个注解,方便开发时进行查看。

  同时,此次引入新的成员是否为空的注解:MemberNotNull 和 MemberNotNullWhen,例如如下的代码:

class UserManager
{
    User user = new User(1, "小米", 1, "123456789"); [MemberNotNull(nameof(user))] public string GetUserName(string id) { return user.Name; } }

  编译器会智能提示:CS8602警告

  

 4、工具类的新变化

  .NET 5.0 改进了Windows窗体设计器,更改了目标框架适用于.NET 5.0及更高版本的方式,更改了WinRT的支持方式,以及其余的一些改进。

  1. Windows窗体设计器:winform设计器

   Windows Forms设计器(用于.NET Core 3.1和.NET 5.0)已经在Visual Studio 16.8中进行了更新,如今支持全部Windows Forms控件。它还支持WinForms控件的Telerik UI。设计器包括您指望的全部设计器功能,包括:拖放,选择,移动和调整大小,剪切/复制/粘贴/删除控件,与属性窗口集成,事件生成等。数据绑定和对更普遍的第三方控件的支持即将推出。

   

   2. .NET 5.0目标框架

   新增一个Console类型工程后,选择目标框架是.NET 5.0, 其Project文件内容是这样的:

   

   新增一个Windows窗体应用工程后,选择目标框架是.NET 5.0, 其Project文件内容是这样的:

   

   Windows桌面API(包括Windows窗体,WPF和WinRT)仅在定位时可用net5.0-windows。同时也能够指定操做系统版本,例如net5.0-windows7或net5.0-windows10.0.17763.0(对于Windows October 2018 Update)。

   若是要使用WinRT API,则须要定位Windows 10版本。

   总结一下:

  • net5.0 是.NET 5.0的新目标框架绰号,Target Framework Moniker(TFM)。
  • net5.0结合并替换netcoreapp和netstandard TFM。
  • net5.0支持.NET Framework兼容模式
  • net5.0-windows 将用于公开Windows特定功能,包括Windows窗体,WPF和WinRT API。
  • .NET 6.0将使用相同的方法,并带有net6.0和将添加net6.0-ios和net6.0-android。
  • 特定于操做系统的TFM能够包含操做系统版本号,例如net6.0-ios14。
  • 可移植的API(如ASP.NET Core)可与一块儿使用net5.0。带有的Xamarin形式也是如此net6.0。

  3. WinRT Interop的重大改进

   在以Windows API为目标这一主题上,微软已经移至一个新模型,以做为.NET 5.0的一部分来支持WinRT API。这包括调用API(在任一方向上; CLR <==> WinRT),两个类型系统之间的数据封送处理以及打算在类型系统或ABI边界上统一对待的类型的统一(即“投影类型” ”,IEnumerable<T>而且IIterable<T>是示例)。

   从.NET 5.0开始,原有的WinRT互操做体系已被移除。这是一个巨大的变化。这意味着使用WinRT和.NET Core 3.x的应用程序和库须要从新开发对接,而且不能按原样在.NET 5.0上运行。

   使用WinRT API的库将须要多目标来管理.NET Core 3.1和.NET 5.0之间的这种差别。

   将来,.NET 将依靠Windows中的WinRT团队提供的新CsWinRT工具。它生成基于C#的WinRT互操做程序集,能够经过NuGet交付该程序集。Windows团队正是针对Windows中的WinRT API所作的。但愿将WinRT(在Windows上)用做互操做系统的任何人均可以使用该工具,以将本机API公开给.NET或将.NET API公开给本机代码。

   关于CsWinRT工具,已经发布了1.0版本,具体能够参考连接:https://blogs.windows.com/windowsdeveloper/2020/11/10/announcing-c-winrt-version-1-0-with-the-net-5-ga-release/

   4. .NET Native Export/ .NET 本地导出

    即本机二进制文件启用导出功能。

   .NET 开发团队的Aaron Robinson一直在从事.NET Native Exports项目,该项目为将.NET组件做为本机库发布提供了更完整的体验。

   .NET Native导出项目可以实现:

  • 公开自定义的本地出口。
  • 不须要像COM这样的高级互操做技术。
  • 跨平台工做

   相似的实现技术,还有:

   5. 事件管道

   事件管道是在.NET Core 2.2中添加的新子系统和API,能够在任何操做系统上执行性能和其余诊断调查。

   在.NET 5.0中,事件管道已获得扩展,以使事件探查器可以写入事件管道事件。

   对于之前依靠ETW(在Windows上)监视应用程序行为和性能的分析探查器,来讲是一个很好的方案和选择。

   这里不作详细展开了。

   6. 转储调试,Dump分析调试

   调试托管代码须要了解托管对象和构造。数据访问组件(DAC)是运行时执行引擎的子集,该引擎具备这些构造的知识,而且能够在没有运行时的状况下访问这些托管对象。

   如今,可使用WinDBG或Windows在Windows上分析在Linux上收集的.NET Core进程转储dotnet dump analyze。

   本次发布还增长了对从macOS上运行的.NET进程捕获ELF转储的支持。因为ELF不是lldbmacOS上的本机可执行文件(像这样的本地调试器将没法与这些转储一块儿使用)文件格式,所以咱们将其设为启用功能。

   要在macOS上支持转储收集,请设置环境变量COMPlus_DbgEnableElfDumpOnMacOS=1。可使用来分析产生的转储dotnet dump analyze。

   7. 打印环境信息

   随着.NET扩展了对新操做系统和芯片体系结构的支持,有时须要一种打印环境信息的方法。.NET 5.0 建立了一个简单的.NET工具来执行此操做,称为dotnet-runtimeinfo。可使用如下命令安装和运行该工具:

dotnet tool install -g dotnet-runtimeinfo
dotnet-runtimeinfo

    

   5、运行时和类库的提高

    1. RyuJIT的代码质量提高

    能够参考这个连接:Performance Improvements in RyuJIT in .NET Core and .NET Framework

    2. GC垃圾回收

  • Card mark stealing – dotnet/coreclr #25986  Server GC (on different threads) can now work-steal while marking gen0/1 objects held live by older generation objects. This means that ephemeral GC pauses are shorter for scenarios where some GC threads took much longer to mark than others.  ServerGC 中标记阶段的耗时更短了
  • Introducing Pinned Object Heap – dotnet/runtime #32283 — Adds the Pinned Object Heap (POH). This new heap (a peer to the Large Object Heap (LOH)) will allow the GC to manage pinned objects separately, and as a result avoid the negative effects of pinned objects on the generational heaps. 新增固定对象堆(POH)。此新堆(与大对象堆(LOH)对等)将容许GC单独管理固定对象,从而避免固定对象对堆的负面影响。
  • Allow allocating large object from free list while background sweeping SOH — Enabled LOH allocations using the free list while BGC is sweeping SOH. Previously this was only using end of segment space on LOH. This allowed for better heap usage.容许在后台扫描SOH时从空闲列表中分配大对象
  • Background GC suspension fixes – dotnet/coreclr #27729 — Suspension fixes to reduce time for both BGC and user threads to be suspended. This reduces the total time it takes to suspend managed threads before a GC can happen. dotnet/coreclr #27578 also contributes to the same outcome. 挂起修复程序可减小BGC和用户线程挂起的时间。这样能够减小发生GC以前挂起托管线程所需的总时间。
  • Fix named cgroup handling in docker — Added support to read limits from named cgroups. Previously we only read from the global one. 修复了docker中命名cgroup处理的问题—添加了对从命名cgroups读取限制的支持
  • Optimize vectorized sorting – dotnet/runtime #37159 — vectorized mark list sorting in GC which reduces the ephemeral GC pause time (also dotnet/runtime #40613). GC中的矢量化标记列表排序,减小了短暂的GC暂停时间
  • Generational aware analysis – dotnet/runtime #40322 — generational aware analysis that allows you to determine what old generation objects hold on to younger generation objects thus making them survive and contribute to ephemeral GC pause time.GC代感知分析,可以肯定哪些旧世代对象保留在年轻代对象上,从而使它们得以生存并有助于短暂的GC暂停时间。
  • Optimize decommitting GC heap memory pages – dotnet/runtime #35896 — optimized decommit, much better decommit logic and for Server GC took decommit completely out of the “stop the world” phase which reduced blocking GC pause time.优化了取消受权,更好的取消受权逻辑,对于Server GC,彻底取消了“中止一切”阶段的受权,从而减小了阻塞GC的暂停时间

     总体总结一下,Server GC延迟更低了,CPU消耗更少、性能更好了。

     3. Windows Arm64的支持

     .NET应用程序如今能够在Windows Arm64上本机运行。在.NET Core 3.0中添加的对Linux Arm64的支持(对glibc和musl的支持)。使用.NET 5.0,能够在Windows Arm64设备(例如Surface Pro X)上开发和运行应用程序。也能够经过x86仿真在Windows Arm64上运行.NET Core和.NET Framework应用程序。可是本机运行Arm64具备更好的性能。

     同时,.NET 5.0 SDK当前在Windows Arm64上不包含Windows桌面组件-Windows窗体和WPF。Windows Arm64上支持SDK,控制台和ASP.NET Core应用程序,但Windows桌面组件不支持。

     4. Arm64性能优化

     .NET 5.0 中主要针对Arm64平台作了如下优化:

  • 调整Arm64的JIT优化(示例
  • 启用并利用Arm64硬件内部函数(示例)。
  • 调整Arm64库中对性能相当重要的算法(示例)。

     更多详细信息,请参见在.NET 5.0中提升Arm64性能

      5. P95 +延迟改进

      Stack Overflow的一位工程师Nick Craver最近分享了他们升级.NET Core后,对延迟的改进:

      问题页面的展示时间中值从大约21毫秒(因为GC而有所增长)降至约15毫秒。

      第95个百分位数从〜40ms降低到〜30ms(相同测量)。第99位从〜60ms降至〜45ms。

      .NET项目组的解读是这样的:固定对象一直是GC性能的长期挑战,由于它们会加速(或致使)内存碎片。.NET 5.0为固定对象添加了新的GC堆。该固定对象堆是基于这样的假设(以空间换时间),他们的存在会致使不相称的性能挑战极少数固定的对象。将固定的对象(尤为是由.NET库做为实现细节建立的对象)移动到惟一的区域是有意义的,而垃圾回收代的GC堆几乎没有或没有固定的对象,所以具备更高的性能。

       6. 分层编译性能改进

       关于分层编译,你们能够参考这个链接:https://devblogs.microsoft.com/dotnet/tiered-compilation-preview-in-net-core-2-1

       在.NET 5.0中对分层编译进行了两项重大改进。下面这2段有点复杂,也比较晦涩

      分层编译的主要机制是调用计数。一旦某个方法被调用了n次,运行时就会要求JIT以更高的质量从新编译该方法。从最先的性能分析中,发现采用计数机制太慢,可是没有找到解决该问题的直接方法。.NET 5.0中改进了分层JIT编译所使用的调用计数机制,以平滑启动期间的性能。在过去的发行版中,已经发如今进程生命周期的前10到15秒钟内,性能会发生不可预测的变化(主要是针对Web服务器)。目前应该已经解决了。

     另外一个性能挑战是对具备循环的方法使用分层编译。根本的问题是,您可使用带有循环屡次的循环的冷方法(仅调用一次或几回; $ lt; n)。咱们称这种病理状况为“冷方法”。热循环”。能够想象Main应用程序的方法会发生这种状况。结果,默认状况下,咱们禁用了带循环方法的分层编译。相反,使应用程序能够选择使用带循环的分层编译。在某些状况下看到了个位数的高性能改进后,PowerShell就是选择执行此操做的应用程序。

     为了更好地解决循环问题,.NET 实现了栈上替换(OSR)。这相似于Java虚拟机具备的同名功能。OSR容许在方法执行过程当中从新编译当前正在运行的方法执行的代码,而这些方法是“堆栈上”活动的。该功能目前处于试验和选择启用状态,而且仅在x64上可用。

     要使用OSR,必须启用多个功能。目前.NET 5.0中没有启用OSR,这个功能还没有决定在生产环境中是否启用,因此这个技术点,了解便可。

      7. JSON序列化 System.Text.Json

      .NET 5.0 对System.Text.Json进行了显着改进,以提升性能和可靠性,同时API尽量地和Newtonsoft.Json相似。它还包括对将JSON对象反序列化对record类型的支持。

      同时微软提供了System.Text.Json替换Newtonsoft.Json的迁移指南。该指南详细阐明了这两个API之间的关系。

      如何从 Newtonsoft.Json 迁移到 System.Text.Json

      JsonSerializer.NET 5.0中的性能显着提升。Stephen Toub在.NET 5中的性能改进中介绍了一些JsonSerializer改进

  6、应用程序部署

    应用程序开发完成后,根据实际的须要,可能会部署到Web服务器,云服务或客户端计算机,或者使用Azure DevOps或GitHub Actions之类的服务进行CI/CD。

    .NET 5.0专一于改善单个文件应用程序,减少docker多阶段构建的容器大小,并为使用.NET Core部署ClickOnce应用程序提供更好的支持。

    1. 容器

    与容器的交互协做很是重要。这个版本中添加了OpenTelemetry支持,能够从应用程序中捕获分布式跟踪和指标。dotnet-monitor是一个新工具,能够做为从.NET进程访问诊断信息的主要工具。特别是,咱们已经开始构建dotnet-monitor的容器变体,您能够将其用做应用程序sidecar。同时,.NET项目组正在构建dotnet / tye,以提升微服务开发人员在开发和部署到Kubernetes环境中的效率。

   .NET运行时如今支持cgroup v2,这个API预计将在2020年之后成为与容器相关的重要API。Docker当前使用cgroup v1(.NET已支持)。相比之下,cgroup v2比cgroup v1更简单,更有效且更安全。.NET 5.0将在cgroup v2环境中正常工做。

   除了Nano Server,微软还将发布Windows Server Core映像,努力减少Windows Server Core映像的大小。

    更小的体积、更低的成本、更快的启动性能。.NET 5.0中将SDK映像从新创建在ASP.NET映像之上,而不是buildpack-deps,这样能够显着减少在多阶段构建方案中提取的聚合映像的大小。

 

   

    2. 单文件应用    

    单个文件应用程序做为单个文件发布和部署。该应用程序及其依赖项都包含在该文件中。当应用程序运行时,依赖项直接从该文件加载到内存中(不影响性能)。

    在.NET 5.0中,单个文件应用程序主要集中在Linux上。它们能够是框架相关的,也能够是独立的。依赖于全局安装的.NET运行时,依赖于框架的单个文件应用程序可能很小。自包含的单文件应用程序较大(因为带有运行时),但不须要做为安装前步骤就安装.NET运行时,所以能够正常工做。一般,依赖框架对开发和企业环境有利,而对于ISV,独立包含一般是更好的选择。

    .NET Core 3.1制做了一个单文件应用程序版本。它将二进制文件打包到一个文件中以进行部署,而后将这些文件解压缩到一个临时目录中以加载并执行它们。在某些状况下,这种方法会更好,可是但愿为5.0构建的解决方案将是首选,而且会受到欢迎。  

    可使用如下命令来生成单文件应用程序:

框架相关的单文件应用程序:
dotnet publish -r linux-x64 --self-contained false /p:PublishSingleFile=true
自包含的单文件应用程序:
dotnet publish -r linux-x64 --self-contained true /p:PublishSingleFile=true

  3. ClickOnce

    ClickOnce一直是流行的.NET部署选项,历史也比较悠久了。.NET Core 3.1和.NET 5.0 Windows应用程序如今支持它。   

    

     以上是.NET 5.0 发布后的技术梳理和整理,.NET 5.0做为.NET技术栈上近几年一个重量级的里程碑,是All in one,统一平台的第一个版本。如今有微软的背书,微软从.NET Preview 1就开始在本身的网站上运行.NET 5, (Bing.com、dot.net已升级并运行了数个月),同时早期的.NET Core版本能够直接升级到.NET 5. 因此你们能够放心使用的。也推荐你们逐步迁移升级到.NET 5.0.

    参考连接:

   https://devblogs.microsoft.com/dotnet/announcing-net-5-0?WT.mc_id=DT-MVP-5003918

   https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/#json?WT.mc_id=DT-MVP-5003918

   https://blogs.windows.com/windowsdeveloper/2020/11/10/announcing-c-winrt-version-1-0-with-the-net-5-ga-release?WT.mc_id=DT-MVP-5003918

 

周国庆

2020/11/15

相关文章
相关标签/搜索