新式 .NET 支持多个操作系统和设备。 .NETNET 开源库支持尽可能多的开发人员(无论是构建 Azure 中托管的 ASP.NET 网站的开发人员,还是在 Unity 中开发 .NET 游戏的开发人员),这一点非常重要。
为库选择目标框架时,必须区分两个不同的目标:
- 新式 API 和模式:库倾向于利用最新版本的 .NET 平台的优势。 您使用现代语言模式,并支持在 CoreCLR 上进行本机提前编译等高级部署模型。
- 广度目标:库支持各种 .NET 实现和版本,以最大限度地提高不同用户方案的兼容性。
这些目标并不总是需要相同的方法。 如果目标应用程序都使用新式 .NET,则库可以面向相同的新式 .NET 版本,而无需面向较旧的框架。
.NET 和 .NET Standard 目标
.NET 和 .NET Standard 目标是为 .NET 库添加跨平台支持的最佳方式。
- .NET 版本 5 到 10 是 .NET 的实现。 每个版本是具有一组统一功能和 API 的单一产品,可用于 Windows 桌面应用和跨平台控制台应用、云服务和网站。
- .NET Standard 是一套 .NET API 规范,在所有 .NET 实现中推出。 以 .NET Standard 为目标可以生成受限于使用给定版本的 .NET Standard 中的 API 的库,这意味着实现该版本的 .NET Standard 的所有平台都可以使用它。
有关 .NET 与 .NET Standard 的对比的详细信息,请参阅 .NET 5 和 .NET Standard。
如果你的项目面向 .NET 或 .NET Standard 并成功编译,这不保证库在所有平台上都能成功运行:
- 特定于平台的 API 将在其他平台上失败。 例如,Microsoft.Win32.Registry 可以在 Windows 上成功运行,但在任何其他操作系统上使用时,会引发 PlatformNotSupportedException。
- API 的行为可能有所不同。 例如,当应用程序在 iOS 或 UWP 上使用提前编译时,反射 API 具有不同的性能特征。
提示
.NET 团队提供平台兼容性分析器,帮助你发现可能存在的问题。
✔️ 务必从包含 net8.0 目标版本或更高版本的新库开始。
对于新库,以现代 .NET(如 .NET 8 或更高版本)为目标,能够访问最新的 API 和性能改进,并启用 AOT 和精简等功能。 新式 .NET 版本是跨平台的,可在 Windows、Linux 和 macOS 之间提供出色的兼容性。
✔️ 如果需要广泛的兼容性或 .NET Framework 支持,请考虑包括 netstandard2.0 目标。
.NET Framework 4.6.1+ 和所有新式 .NET 实现都支持 .NET Standard 2.0。 如果需要支持 .NET Framework 应用程序,或者在生成库以实现不同 .NET 生态系统的最大兼容性时,请包含此目标。
❌ 请避免包含 netstandard1.x 目标。
.NET Standard 1.x 作为一组精细的 NuGet 包分发,它创建了一个大型的包依赖项关系图,并导致在构建时需下载大量的包。 新式 .NET 实现支持 .NET Standard 2.0。 如果需要专门面向较旧的平台,则只能面向 .NET Standard。
✔️ 如果需要面向 netstandard2.0 目标,请务必包括 netstandard1.x 目标。
所有支持 .NET Standard 2.0 的平台都将使用
netstandard2.0目标,并从较小的包关系图中受益,而较旧的平台仍然可以正常运行并回退到使用netstandard1.x目标。
❌ 如果库依赖于平台特定的应用模型,请避免包括 .NET Standard 目标。
例如,UWP 控件工具包库依赖的应用模型只能在 UWP 上使用。 特定于应用模型 API 在 .NET Standard 中不可用。
❌ 如果你的项目或依赖项具有多目标,请不要面向 netstandard2.0 发布。
netstandard2.0不是运行时,多目标项目会提供特定于运行时框架的库,当在其他框架上运行时将需要此库。
多目标
有时,需要从库中访问特定于框架的 API。 调用特定于框架的 API 的最佳方法是使用多目标,它会为许多 .NET 目标框架(而不是仅为一个)构建项目。
为了让使用者不必针对各个框架构建,应该尽量获取 .NET Standard 输出以及一个或多个特定于框架的输出。 通过多目标,所有程序集都打包在一个 NuGet 包中。 然后,使用者可以引用同一个包,NuGet 将选择适当的实现。 你的 .NET Standard 库充当在任何地方使用的回退库(NuGet 包提供特定于框架的实现的情况除外)。 通过多目标可以在代码中使用条件编译并调用特定于框架的 API。
✔️ 请在面向 .NET Standard 的基础上考虑面向 .NET 实现。
针对 .NET 实现,您可以调用超出 .NET Standard 范畴的特定平台 API。
执行此操作时,请不要放弃对 .NET Standard 的支持。 相反,从实现引发并提供功能 API。 这样一来,你的库就可以在任何地方使用,并支持运行时启动功能。
public static class GpsLocation
{
// This project uses multi-targeting to expose device-specific APIs to .NET Standard.
public static async Task<(double latitude, double longitude)> GetCoordinatesAsync()
{
#if NET462
return CallDotNetFrameworkApi();
#elif WINDOWS_UWP
return CallUwpApi();
#else
throw new PlatformNotSupportedException();
#endif
}
// Allows callers to check without having to catch PlatformNotSupportedException
// or replicating the OS check.
public static bool IsSupported
{
get
{
#if NET462 || WINDOWS_UWP
return true;
#else
return false;
#endif
}
}
}
✔️ 如果项目具有任何库或包依赖项,请考虑多目标,即使源代码对所有目标均相同。
在每个目标框架的不同依赖程序集版本中包装时,项目的依赖包(直接包或下游包)可以使用相同的代码 API。 添加特定目标可确保使用者无需添加或更新其程序集绑定重定向。
❌ 如果你的源代码对所有目标都相同且项目没有任何库或包依赖项,请避免使用多目标或面向 .NET Standard。
.NET Standard 程序集将自动供 NuGet 使用。 面向单个 .NET 实现会增加
*.nupkg大小,不会提供任何好处。
✔️ 考虑在同时面向netstandard2.0时为net462添加一个目标。
在 .NET Framework 中使用 .NET Standard 2.0 存在一些问题,这些在 .NET Framework 4.7.2 中已得到解决。 你可以为仍在使用 NET Framework 4.6.2-4.7.1 的开发人员提供为 .NET Framework 4.6.2 构建的二进制文件,从而改善其体验。
✔️ 请使用 NuGet 包分发您的库。
NuGet 将为开发人员选择最佳目标,使他们无需选择适当实现。
✔️ 请务必在使用多目标时使用项目文件的 TargetFrameworks 属性。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- This project will output net8.0 and netstandard2.0 assemblies -->
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>
</Project>
❌ 避免更改程序集名称或为库编译的每个 TFM 使用不同的程序集名称。 由于库之间的依赖关系,每个 TFM 具有不同程序集名称的多目标可能会中断包使用者。 程序集在所有 TTFM 中应具有相同的名称。
较旧的目标
.NET 支持面向不再受支持的 .NET Framework 版本以及不再经常使用的平台。 尽管使库在尽可能多的目标上工作是有价值的,但这种情况下必须解决缺少的 API 问题,这会增加很大的开销。 考虑到某些框架的使用范围和局限性,不再值得面向它们。
❌ 请勿包括可移植类库 (PCL) 目标。 例如,portable-net45+win8+wpa81+wp8 。
.NET standard 是支持跨平台 .NET 库的新式方法,它可以替代 PCL。
❌ 请避免包括面向不再受支持的 .NET 平台的目标。 例如:SL4、WP。