将 .NET 5.0 目标添加到我的 NuGet 包

Adding .NET 5.0 target to my NuGet packages

我发布了几十个 NuGet 包。大多数以 .NET Standard 为目标。 Visual Studio 使我每次编译时更新这些包变得非常容易。

现在,我想升级这些包以充分利用 .NET 5.0。但我希望现有的那些仍然可用于那些无法升级到 .NET 5.0 的用户。另外,我不想创建全新的包,这样我每个包都有两个版本。

如果我没记错的话,一个 NuGet 包可以针对多个框架版本,我认为 Visual Studio 会在安装到项目中时自动加载正确的版本。但是,创建多个包目标并未纳入 Visual Studio.

不知道这里有没有NuGet包高手。我想了解这是否可行、实现它的最简单方法是什么、如何处理版本控制等。有什么好的书籍或文章参考吗?

补充说明

我知道我可以在 project 级别定位多个框架(使用项目文件中的 TargetFrameworks 元素)。但是如果 #ifelseendif 块,我必须填写我的代码以利用每个目标框架。我不认为我想要这个。我的库方法签名将更改以利用可为空字符串之类的东西。我希望能够自由地执行此操作,而无需创建所有内容的多个版本。

要走的路绝对是在 csproj 中使用 <TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks> 多目标你的 nugets。然后还启用可空值并将 LangVersion 设置为 v9.0.

完成此操作后,根据您现有的代码,您可能必须根据目标调整代码,因为 .NET Standard 2 将不支持所有 .NET 5...

然而,尽管我怀疑你能否完全摆脱 #if 守卫,但还是有一些解决方案。

首先我们要区分netstandard2.0之后(即.NET 4.6.1和.NET Core 2.1时代之后)出现的3种新奇

  • 仅依赖于编译器的新功能:这些功能仅需要最新的编译器,但一旦编译就可以被旧版本的 .NET 使用,因为它们保持完整的 IL 兼容性并且不需要运行时程序集中的新类型。抛出表达式,例如静态(或非静态)局部函数。
  • 依赖于运行时的功能:这些功能(例如 Span<T>)需要最新的 CLR。如果 nuget 包必须支持 .NET Standard 2 目标,这些是您要避免的(至少在 public 界面中)。
  • 中间特性 ;) 这些特性主要由编译器支持,但在运行时也有不太重要的支持。可空值和记录属于这一类:它们依赖于一些属性(以及记录案例中的一些新 IL 元数据),但鉴于这些属性是在运行时提供的,它们可以在 netstandard2.0 目标中使用。

现在,让我们看看我们可以做些什么来避免太多 #if。第一个去的地方是 csproj:

<PropertyGroup>
  <TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
  <!-- enable the feature but without warning for legacy frameworks to avoid false positives. -->
  <Nullable>annotations</Nullable>
  <!-- enable the feature including warnings only on top of the most recent framework you target. -->
  <!-- it will serve as a reference for warnings for all others. -->
  <Nullable Condition="'$(TargetFramework)' == 'net5.0'">enable</Nullable>
  <LangVersion>9.0</LangVersion>
</PropertyGroup>
  • 我的目标是 netstandard2.0net5.0
  • 我把语言版本设置为9.0这样我就可以使用所有最新的花里胡哨
  • Nullable 的声明有点复杂,但基本上,它可以防止针对 netstandard2.0 目标弹出警告,同时完全支持 .net5.0 目标。

然后让我们添加这两个神奇的 nuget 包:

<ItemGroup>
  <PackageReference Include="IsExternalInit" Version="1.0.0">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
  <PackageReference Include="Nullable" Version="1.3.0">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
</ItemGroup>

这 2 个包由同一个人提供(参见 here and here)。它们是仅限开发人员的依赖项,因此不会污染依赖链中的消费者。他们所做的只是声明支持可空值和记录所需的属性(作为内部 类 以免与合法属性冲突)。

通过此设置,您现在可以在代码库中使用记录和可空​​值,并且它将成功编译到两个目标。

如果您使用来自 net5.0 客户端的程序集,一切都会按预期进行。然后,如果您从 .NET 4.8 程序集使用它,事情也会正常:当然,您不会有可为空的警告或智能感知,并且记录将被视为常规 类,但我会说它很好地降级了。

对于最后一种功能,真正依赖于运行时的功能,如果您想使用它们,您别无选择,只能#if。假设您想用 Span<T> 替换一些基于数组的代码...您需要提供两个私有实现和 #if select 正确的实现,具体取决于目标(参见 https://docs.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-a-target-framework 以获得支持的预处理器常量列表)。

我没有详尽地介绍 .NET Standard 2 和 .NET 5 之间发生的所有变化(那可能是一本书),但这应该可以帮助您入门。我敢肯定,聪明的人会逐个找到解决方案,尽可能将它们转移到旧版本的 .NET。

我也把我的测试游乐场推到了这个github repository,如果你想看看

最后一点,我想说最重要的是进行全面的测试,以确保代码库的现代化不会破坏旧客户的任何东西。