.NET标准的未来

2020-09-21 23:13:38

自从.NET5发布以来,你们中的许多人都在问,这对.NET标准意味着什么,它是否仍然有意义。在这篇文章中,我将解释.NET5如何改善代码共享并取代.NET标准。我还将介绍您仍然需要.NET标准版的情况。

.NET 5将是具有统一功能集和API的单一产品,可用于Windows桌面应用程序、跨平台移动应用程序、控制台应用程序、云服务和网站:

Net5.0。这适用于在任何地方运行的代码。它组合并替换了netcoreapp和netStandard名称。这个TFM通常只包括跨平台工作的技术(除了实用的让步,就像我们在.NET标准中已经做的那样)。

Net5.0-windows(以及更高版本的net6.0-Android和net6.0-iOS)。这些TFM代表特定于操作系统的.NET5风格,包括Net5.0以及特定于操作系统的功能。

我们不会发布.NET标准的新版本,但.NET5和所有未来版本将继续支持.NET标准2.1及更早版本。您应该考虑将net5.0(和未来版本)作为向前共享代码演示的基础。

由于net5.0是所有这些新的tfm的共享基础,这意味着运行时、库和新的语言特性都围绕这个版本号进行协调。例如,为了使用C#9,您需要使用net5.0或net5.0-windows。

.NET 5和所有未来版本都将始终支持.NET Standard 2.1及更高版本。从.NET标准重定向到.NET5的唯一原因是为了获得更多运行时特性、语言特性或API的访问权。因此,您可以将.NET5视为.NET Standard vNext。

那新代码呢?您应该仍然从.NET Standard 2.0开始,还是应该直接转到.NET5?那得看情况。

应用程序组件。如果您使用库将应用程序分解为几个组件,我建议使用netX.Y,其中X.Y是您的应用程序(或多个应用程序)面向的最低数量的.NET。为简单起见,您可能希望组成应用程序的所有项目都在同一版本的.NET上,因为这意味着您可以假定在任何地方都有相同的BCL功能。

可重用的库。如果您正在构建您计划在NuGet上发布的可复用库,那么您将需要考虑Reach和可用的特性集之间的权衡。.NET Standard 2.0是.NET Framework支持的.NETStandard的最高版本,因此它将为您提供最好的版本,同时也为您提供了相当大的功能集可供使用。我们一般建议不要以.NET Standard 1.x为目标,因为这不值得再争论了。如果您不需要支持.NET Framework,那么您可以使用.NET Standard 2.1或.NET 5。大多数代码可能可以跳过.NETStandard 2.1而直接转到.NET 5。

那么,你应该怎么做呢?我的期望是,广泛使用的库最终将同时适用于.NET Standard 2.0和.NET 5:支持.NET Standard2.0为您提供了最大的覆盖范围,同时支持.NET 5确保您可以为已经使用.NET 5的客户利用最新的平台功能。

再过几年,可重用库的选择将只涉及netX.Y的版本号,这基本上就是为.Net构建库的方式-您通常希望支持一些较旧的版本,以确保获得最大的影响力。

.NET标准使创建可以在所有.NET平台上运行的库变得容易得多。但是.NET标准版仍然存在三个问题:

它公开了特定于平台的特性,这意味着您不能静态地验证您的代码是否真正可移植。

.NET标准是在.NET平台没有在实现级别融合的时候设计的。这使得编写需要在不同环境中工作的代码变得困难,因为不同的工作负载使用不同的.NET实现。

NET标准的目标是统一基类库(BCL)的功能集,这样您就可以编写可以在任何地方运行的单个库。这为我们提供了很好的服务:超过77%的前1000个包支持.NET标准。如果我们看看NuGet.org上在过去6个月内更新的所有软件包,采用率为58%。

但是,仅将API集标准化就会产生税收。每当我们添加新的API时,它都需要协调--这是经常发生的事情。.NET开源社区(包括.NET团队)通过提供新的语言功能、可用性改进、新的横切功能(如Span<;T>;)或支持新的数据格式或网络协议,不断在BCL中创新。

虽然我们可以提供新类型作为NuGet包,但我们不能以这种方式提供新的Apison现有类型。因此,从一般意义上讲,BCL的创新需要发布新版本的.NET标准。

在.NET Standard 2.0之前,这并不是什么问题,因为我们只标准化了现有的API。但是在.NET Standard 2.1中,我们对全新的API进行了标准化,这就是我们看到相当多摩擦的地方。

NET标准是所有.NET实现都必须支持的API集,因此它有一个编辑方面,即所有API都必须经过.NET标准审查委员会的审查。该委员会由.NET平台实现者和.NET社区的代表组成,目标是只标准化我们可以在所有当前和未来的.NET平台上真正实现的API。这些审查是必要的,因为.NET堆栈有不同的实现,具有不同的约束。

我们预测到了这种类型的摩擦,这就是为什么我们在早些时候说.NETStandard将只标准化至少在一个.NET实现中已经提供的API。乍一看,这似乎是合理的,但后来您意识到.NET Standard不能经常发布。因此,如果某个特性错过了某个特定版本,您可能需要等待几年才能使用它,甚至可能需要等待更长时间,直到这个版本的.NETStandard得到广泛支持。

我们觉得有些功能失去的机会太多,所以我们做了一些不自然的事情来标准化那些还没有发布的API(比如IAsyncEnumerable<;T>;)。对所有特性都这样做太昂贵了,这就是为什么他们中相当多的人仍然错过了.NET Standard 2.1的培训(比如新的硬件特性)。

但是,如果只有一个代码库呢?如果该代码库必须支持今天使.NET实现不同的所有方面,例如既支持即时(JIT)编译又支持提前(AOT)编译,情况会怎样呢?

我们从一开始就将所有这些方面作为功能设计的一部分,而不是事后再做这些评论。在这样的世界里,标准化的API集合通过构造就是公共API集合。当一个特性被简单实现时,它就已经对每个人都可用了,因为代码库是共享的。

将API集与其实现分开不仅会降低API的可用性。这也意味着我们需要将.NET标准版本映射到它们的实现。作为一个多年来不得不向许多人解释这张桌子的人,我开始意识到这个看似简单的想法是多么复杂。我们已经尽了最大努力让它变得更容易,但最终,这只是固有的复杂性,因为API集和实现是独立提供的。

我们通过在.NET平台下面添加另一个代表公共API集的合成平台来统一.NET平台。在一个非常真实的意义上,这部以XKCD为灵感的漫画恰到好处:

如果不在我们的层图中真正合并一些矩形,我们就不能解决这个问题,这就是.NET5所做的:它提供了一个统一的实现,所有各方都构建在相同的基础上,从而获得相同的API形状和版本号。

当我们设计.NET标准时,为了避免过多地破坏库生态系统,我们不得不做出务实的让步。也就是说,我们必须包括一些仅限Windows的API(如文件系统ACL、注册表、WMI等)。展望未来,我们将避免在net5.0、net6.0和未来版本中添加特定于平台的API。然而,我们不可能预测未来。例如,在Blazor WebAssembly中,我们最近添加了一个运行.NET的新环境,一些原本跨平台的API(如线程或进程控制)在浏览器的沙箱中不能被支持。

你们中的许多人抱怨说,这些类型的API感觉就像地雷-代码编译时没有错误,因此看起来可以移植到任何平台上,但当在没有给定API实现的平台上运行时,就会出现运行时错误。

从.NET5开始,我们附带了默认开启的SDK的分析器和代码修复程序。这包括平台兼容性分析器,它可以检测您打算在其上运行的平台不支持的API的无意使用。此功能取代了Microsoft.DotNet.Analyzers.Compatibility NuGet软件包。

当您创建一个面向Net5.0的项目时,您可以引用Microsoft.Win32.Registry包。但当您开始使用它时,会收到以下警告:

私有静态字符串GetLoggingDirectory(){Using(RegistryKey Key=Registry.CurrentUser.OpenSubKey(@";Software\Fabrikam";)){if(key?.GetValue(";LoggingDirectoryPath";)is string configuredPath)return configuredPath;}string exePath=Process.GetCurrentProcess().MainModule.FileName;String Folder=Path.GetDirectoryName(ExePath);return Path.Combine(Folder,";Logging";);}

CA1416:';Windows';支持';Windows';CA1416:';Registry.CurrentUser';CA1416:';RegistryKey.GetValue(String?)';支持';Windows';支持';RegistryKey.OpenSubKey(String)';

守住电话。您可以使用OperatingSystem.IsWindows()在调用API之前检查是否在Windows上运行。

将呼叫标记为特定于Windows。在某些情况下,通过[支持的OSPlatform(";Windows";)]将调用成员标记为特定于平台可能是有意义的。

删除代码。通常不是您想要的,因为这意味着当Windows用户使用您的代码时,您会失去保真度,但是对于存在跨平台替代方案的情况,您可能会更好地使用跨平台特定的API。例如,您可以不使用注册表,而使用XML配置文件。

取消显示警告。当然,您可以通过.editorconfig或#杂注警告禁用来欺骗并简单地取消警告。但是,在使用特定于平台的API时,您应该首选选项(1)和(2)。

要保护调用,请在System.OperatingSystem类上使用新的静态方法,例如:

私有静态字符串GetLoggingDirectory(){if(OperatingSystem.IsWindows()){Using(RegistryKey Key=Registry.CurrentUser.OpenSubKey(@";Software\Fabrikam";)){if(key?.GetValue(";LoggingDirectoryPath";)is string configuredPath)return configuredPath;}}string exePath=Process.GetCurrentProcess().MainModule.FileName;String Folder=Path.GetDirectoryName(ExePath);return Path.Combine(Folder,";Logging";);}。

[SupportedOSPlatform(";windows";)]private静态字符串GetLoggingDirectory(){Using(RegistryKey Key=Registry.CurrentUser.OpenSubKey(@";Software\Fabrikam";)){if(key?.GetValue(";LoggingDirectoryPath";)is string configuredPath)return configuredPath;}string exePath=Process.GetCurrentProcess().MainModule.FileName;String Folder=Path.GetDirectoryName(ExePath);return Path.Combine(Folder,";Logging";);}

主要区别在于,在第二个示例中,分析器现在将对GetLoggingDirectory()的调用点发出警告,因为它现在被认为是特定于Windows的API。换句话说,您将执行平台检查的要求转发给您的调用者。

[Supported dOSPlatform]属性可以应用于成员、类型或程序集级别。此属性也由BCL本身使用。例如,程序集Microsoft.Win32.Registry应用了此属性,因此分析器首先知道注册表是特定于Windows的API。

请注意,如果您的目标是net5.0-windows,则此属性将自动应用于您的程序集。这意味着在net5.0中使用特定于Windows的API-Windows永远不会生成任何警告,因为您的整个程序集被认为是特定于Windows的。

Blazor WebAssembly项目在浏览器沙箱中运行,这限制了您可以使用哪些API。例如,虽然线程和进程创建都是跨平台的API,但我们不能让这些API在Blazor WebAssembly中工作,这意味着它们会抛出PlatformNotSupportdException。我们已经将这些接口标记为[UnsupportedOSPlatform(";Browser";)]。

假设您将GetLoggingDirectory()方法复制和粘贴到Blazor WebAssemblyapplication中。

私有静态字符串GetLoggingDirectory(){//...。String exePath=Process.GetCurrentProcess().MainModule.FileName;String Folder=Path.GetDirectoryName(ExePath);return Path.Combine(Folder,";Logging";);}。

私有静态字符串GetLoggingDirectory(){//...。If(!OperatingSystem.IsBrowser()){string exePath=Process.GetCurrentProcess().MainModule.FileName;String Folder=Path.GetDirectoryName(ExePath);return Path.Combine(Folder,";Logging";);}Else{Return String.Empty;}}。

[UnsupportedOSPlatform(";browser";)]private静态字符串GetLoggingDirectory(){//...。String exePath=Process.GetCurrentProcess().MainModule.FileName;String Folder=Path.GetDirectoryName(ExePath);return Path.Combine(Folder,";Logging";);}

因为浏览器沙箱是相当严格的,所以并不是所有的类库和NuGet包都可以在Blazor WebAssembly中工作。此外,预计绝大多数库也不支持在BlazorWebAssembly中运行。

这就是为什么针对net5.0的常规类库不会看到Blazor WebAssembly不支持的API的警告。您必须明确表示您打算通过将<;SupportdPlatform>;项添加到项目文件来支持Blazor Web Assembly中的项目:

如果您正在构建Blazor WebAssembly应用程序,则不必执行此操作,因为Microsoft.NET.Sdk.BlazorWebAssembly SDK会自动执行此操作。

NET5及其后续版本将是一个单一的代码库,支持桌面应用、移动应用、云服务、网站以及.NET未来将运行的任何环境。

您可能会想,等等,这听起来不错,但是如果有人想要创建一个全新的实现怎么办?那也很好。但几乎没有人会白手起家。最有可能的是,它将是当前代码库(DotNet/Runtime)的分支。例如,Tizen(三星的智能家电平台)使用的是.NET Core,改动最少,最上面是三星专用的appmodel。

分叉保留了合并关系,这允许维护人员保持从DotNet/运行时回购中引入新的更改,从而受益于不受其更改影响的领域中的BCL创新。这与Linux发行版的工作方式非常相似。

诚然,在某些情况下,人们可能想要创建一个非常不同的.NET类型,例如没有当前BCL的最小运行时。但这将意味着它无论如何都不能利用现有的.NET库生态系统,这意味着它也不会实现.NET标准。我们通常对追求这个方向不感兴趣,但是.NET标准和.NET核心的融合并不能阻止这一点,也不会使它变得更加困难。

作为一名库作者,您可能想知道.NET5何时会得到广泛支持。展望未来,我们将在每年11月发布.NET,每隔一年发布一次长期支持(LTS)版本。

.NET 5将于2020年11月发布,.NET6将于2021年11月作为anLTS发布。我们创建这个固定的时间表是为了让您更容易计划更新(如果您是应用程序开发人员),并预测对支持的.NET版本的需求(如果您是库开发人员)。

多亏了并行安装.NET Core的能力,新版本被采用得相当快,其中LTS版本是最受欢迎的。事实上,.NET Core3.1是有史以来采用最快的.NET版本。

我们的期望是,每次我们发布时,我们都会联合发布所有的框架名称。例如,它可能如下所示:

这意味着你可以普遍预期,无论我们在BCL中做了什么创新,你都将能够在所有应用程序模型中使用它,无论它们运行在什么平台上。这也意味着,只要您运行的是最新版本的应用程序,为最新的网络框架提供的库总是可以从所有应用程序型号中使用。

此模型消除了.NET标准版本控制的复杂性,因为每次我们发布时,您都可以假设所有平台都将立即完全支持新版本。我们通过使用前缀命名约定来巩固这一承诺。

新版本的.NET可能会添加对其他平台的支持。例如,我们将在.NET6中添加对Android和iOS的支持,相反,我们可能会停止支持不再相关的平台。NET6中不存在的假装net5.0出头的目标框架就说明了这一点。我们没有放弃平台的计划,但是这个模型支持它。这将是一件大事,预计不会发生,而且会提前很久宣布。这与我们在.NET Standard中使用的模型相同,例如,有一个非新版本的Windows Phone实现了较新版本的.NET Standard。

我们最初考虑为WebAssembly添加TFM,例如net5.0-wasm。反对这一决定的原因如下:

WebAssembly更像是指令集(例如x86或x64),而不是操作系统。而且我们通常不会在不同的架构之间提供不同的API。

WebAssembly在浏览器沙箱中的执行模型是一个关键的区别,但我们认为仅将其建模为运行时检查更有意义。与检查Windows和Linux的方式类似,您可以使用OperatingSystem类型。因为这与指令集无关,所以该方法被称为IsBrowser(),而不是IsWebAssembly()。

WebAssembly有运行时标识符(RID),称为Browser和Browser-wasm。它们允许包作者在浏览器中以WebAssembly为目标时部署不同的二进制文件。这对于需要事先编译成Web程序集的本机代码特别有用。

如上所述,我们已经标记了浏览器沙箱中不支持的API,例如System.Diagnotics.Process。如果您从浏览器应用程序内部使用这些API,您将收到一条警告,告诉您此API不受支持。

Net5.0适用于在任何地方运行的代码。它组合并替换了netcoreapp和netStandard名称。我们还有特定于平台的框架,比如net5.0-windows(之后还有net6.0-android和net6.0-iOS)。

由于该标准与其实现之间没有区别,因此您将能够比使用.NET标准版更快地利用新功能。此外,由于命名约定,您将能够轻松地辨别谁可以使用给定库-而不必参考.NET标准版本表。

虽然.NET Standard 2.1将是.NET Standard的最后一个版本,但.NET 5和以后的所有版本都将继续支持.NET Standard 2.1及更早版本。您应该将net5.0(以及未来的版本)看作是共享代码向前发展的基础。