跨平台定位
新式 .NET 支持多个操作系统和设备。 .NETNET 开源库支持尽可能多的开发人员(无论是构建 Azure 中托管的 ASP.NET 网站的开发人员,还是在 Unity 中开发 .NET 游戏的开发人员),这一点非常重要。
.NET 和 .NET Standard 目标
.NET 和 .NET Standard 目标是为 .NET 库添加跨平台支持的最佳方式。
- .NET Standard 是一套 .NET API 规范,在所有 .NET 实现中推出。 以 .NET Standard 为目标可以生成受限于使用给定版本的 .NET Standard 中的 API 的库,这意味着实现该版本的 .NET Standard 的所有平台都可以使用它。
- .NET 6-8 是 .NET 的实现。 每个版本是具有一组统一功能和 API 的单一产品,可用于 Windows 桌面应用和跨平台控制台应用、云服务和网站。
有关 .NET 与 .NET Standard 的对比的详细信息,请参阅 .NET 5 和 .NET Standard。
如果你的项目面向 .NET 或 .NET Standard 并成功编译,这不保证库在所有平台上都能成功运行:
- 特定于平台的 API 将在其他平台上失败。 例如,Microsoft.Win32.Registry 可以在 Windows 上成功运行,但在任何其他操作系统上使用时,会引发 PlatformNotSupportedException。
- API 的行为可能有所不同。 例如,当应用程序在 iOS 或 UWP 上使用提前编译时,反射 API 具有不同的性能特征。
提示
.NET 团队提供平台兼容性分析器,帮助你发现可能存在的问题。
✔️ 请务必首先包含一个 netstandard2.0
目标。
最常规用途的库不需要 .NET Standard 2.0 以外的 API。 所有新式平台都支持 .NET Standard 2.0,并且它是支持具有一个目标的多个平台的推荐方法。 如果你不需要支持 .NET Framework,则也可以面向 .NET Standard 2.1。
✔️ 如果需要在新式 .NET 中引入的新 API,务必包含一个 net6.0
(或更高版本)目标。
.NET 6 及更高版本的应用可以使用
netstandard2.0
目标,所以net6.0
不是必需的。 应显式设定目标net6.0
、net7.0
net8.0
或net9.0
想要使用较新的 .NET API 时。
❌ 请避免包含 netstandard1.x
目标。
.NET Standard 1.x 作为一组精细的 NuGet 包分发,它创建了一个大型的包依赖项关系图,并导致在构建时需下载大量的包。 新式 .NET 实现支持 .NET Standard 2.0。 如果需要专门面向较旧的平台,则只能面向 .NET Standard。
✔️ 如果需要面向 netstandard1.x
目标,请务必包括 netstandard2.0
目标。
所有支持 .NET Standard 2.0 的平台都将使用
netstandard2.0
目标,并从较小的包关系图中受益,而较旧的平台仍然可以正常运行并回退到使用netstandard1.x
目标。
❌ 如果库依赖于平台特定的应用模型,请避免包括 .NET Standard 目标。
例如,UWP 控件工具包库依赖的应用模型只能在 UWP 上使用。 特定于应用模型 API 在 .NET Standard 中不可用。
多目标
有时,需要从库中访问特定于框架的 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 netstandard2.0 and net462 assemblies -->
<TargetFrameworks>netstandard2.0;net462</TargetFrameworks>
</PropertyGroup>
</Project>
✔️ 请考虑在对 UWP 和 Xamarin 进行多目标定位时使用 MSBuild.Sdk.Extras,它可以极大地简化项目文件。
❌ 避免更改程序集名称或为库编译的每个 TFM 使用不同的程序集名称。 由于库之间的依赖关系,每个 TFM 具有不同程序集名称的多目标可能会中断包使用者。 程序集在所有 TTFM 中应具有相同的名称。
较旧的目标
.NET 支持面向不再受支持的 .NET Framework 版本以及不再经常使用的平台。 尽管使库在尽可能多的目标上工作是有价值的,但这种情况下必须解决缺少的 API 问题,这会增加很大的开销。 考虑到某些框架的使用范围和局限性,不再值得面向它们。
❌ 请避免包括可移植类库 (PCL) 目标。 例如,portable-net45+win8+wpa81+wp8
。
.NET standard 是支持跨平台 .NET 库的新式方法,它可以替代 PCL。
❌ 请避免包括面向不再受支持的 .NET 平台的目标。 例如:SL4
、WP
。