CA1416:验证平台兼容性

属性
规则 ID CA1416
标题 验证平台兼容性
类别 互操作性
修复是中断修复还是非中断修复 非中断
在 .NET 9 中默认启用 作为警告

原因

如果特定于平台的 API 用于其他平台的上下文中,或者平台未经过验证(平台中立),规则将报告违规。 如果你使用项目的目标平台不支持的 API,规则也会报告违规。

默认情况下,此规则只对面向 .NET 5 或更高版本的项目启用。 但是,可以为面向其他框架的项目启用该分析器。

规则说明

.NET 5 增加了新属性(SupportedOSPlatformAttributeUnsupportedOSPlatformAttribute),用于为特定于平台的 API 添加注释。 可以使用或不使用作为平台名称一部分的版本号对两个属性进行实例化。 它们还可以在不同平台应用多次。

  • 未加注释的 API 被视为可用于所有操作系统 (OS) 平台。
  • 标记为 [SupportedOSPlatform("platformName")] 的 API 被视为只能移植到指定 OS 平台。 如果该平台是另一个平台的一部分,则该属性意味着该平台也受支持。
  • 标记为 [UnsupportedOSPlatform("platformName")] 的 API 被视为在指定 OS 平台上不受支持。 如果该平台是另一个平台的一部分,则该属性意味着该平台也不受支持。

可以在单个 API 上合并 [SupportedOSPlatform][UnsupportedOSPlatform] 属性。 在这种情况下,下列规则适用:

  • 允许列表。 如果每个 OS 平台的最低版本是 [SupportedOSPlatform] 属性,则 API 会被视为仅在列出的平台上受支持,但在所有其他平台上不受支持。 此列表的 [UnsupportedOSPlatform] 属性可能包含相同的平台,但只有更高版本,这表示 API 已从该版本中删除。
  • 拒绝列表。 如果每个 OS 平台的最低版本是 [UnsupportedOSPlatform] 属性,则 API 会被视为仅在列出的平台上不受支持,但在所有其他平台上受支持。 此列表的 [SupportedOSPlatform] 属性可能包含相同的平台,但只有更高版本,这表示从该版本开始支持 API。
  • 不一致的列表。 如果某些平台的最低版本是 [SupportedOSPlatform],但其他平台的是 [UnsupportedOSPlatform],则此组合被视为不一致。 API 上的某些注释将被忽略。 将来,我们可能会引入一个在不一致时生成警告的分析器。

如果从其他平台的上下文中访问使用这些属性批注的 API,则可以看到 CA1416 冲突。

TFM 目标平台

分析器不会检查来自 MSBuild 属性的目标框架名字对象 (TFM) 目标平台<TargetFramework><TargetFrameworks>。 如果 TFM 具有目标平台,.NET SDK 会在 AssemblyInfo.cs 文件中注入带有目标平台名称的 SupportedOSPlatform 属性,该文件由分析器使用。 例如,如果 TFM 是 net5.0-windows10.0.19041,SDK 会将 [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.19041")] 属性注入 AssemblyInfo.cs 文件,整个程序集被视为仅限 Windows。 因此,调用版本 7.0 或以下的仅限 Windows 的 API 不会导致项目中出现警告。

注意

如果禁用项目的 AssemblyInfo.cs 文件生成(即 <GenerateAssemblyInfo> 属性设置为 false),则无法由 SDK 添加所需的程序集级别 SupportedOSPlatform 属性。 在这种情况下,即使面向该平台,也会出现特定于平台的 API 使用情况的警告。 若要解决警告,请启用 AssemblyInfo.cs 文件生成或在项目中手动添加属性。

冲突

  • 从其他平台可访问的代码中访问仅在指定平台 ([SupportedOSPlatform("platformName")]) 上受支持的 API 时,将看到以下违规行为:“platformName”上支持“API”。

    // An API supported only on Linux.
    [SupportedOSPlatform("linux")]
    public void LinuxOnlyApi() { }
    
    // API is supported on Windows, iOS from version 14.0, and MacCatalyst from version 14.0.
    [SupportedOSPlatform("windows")]
    [SupportedOSPlatform("ios14.0")] // MacCatalyst is a superset of iOS, therefore it's also supported.
    public void SupportedOnWindowsIos14AndMacCatalyst14() { }
    
    public void Caller()
    {
        LinuxOnlyApi(); // This call site is reachable on all platforms. 'LinuxOnlyApi()' is only supported on: 'linux'
    
        SupportedOnWindowsIos14AndMacCatalyst14(); // This call site is reachable on all platforms. 'SupportedOnWindowsIos14AndMacCatalyst14()'
                                                   // is only supported on: 'windows', 'ios' 14.0 and later, 'MacCatalyst' 14.0 and later.
    }
    

    注意

    只有当项目未面向支持的平台 (net5.0-differentPlatform) 时,才会发生违规行为。 这同样适用于多目标项目。 如果项目面向指定的平台 (net5.0-platformName) 并且为项目启用了 AssemblyInfo.cs 文件生成,则不会发生冲突。

  • 从面向不受支持的平台上下文中访问具有 [UnsupportedOSPlatform("platformName")] 属性的 API 时,将发生以下违规行为:“platformName”上不支持“API”。

    // An API not supported on Android but supported on all other platforms.
    [UnsupportedOSPlatform("android")]
    public void DoesNotWorkOnAndroid() { }
    
    // An API was unsupported on Windows until version 10.0.18362.
    // The API is considered supported everywhere else without constraints.
    [UnsupportedOSPlatform("windows")]
    [SupportedOSPlatform("windows10.0.18362")]
    public void StartedWindowsSupportFromCertainVersion() { }
    
    public void Caller()
    {
        DoesNotWorkOnAndroid(); // This call site is reachable on all platforms.'DoesNotWorkOnAndroid()' is unsupported on: 'android'
    
        StartedWindowsSupportFromCertainVersion(); // This call site is reachable on all platforms. 'StartedWindowsSupportFromCertainVersion()' is unsupported on: 'windows' 10.0.18362 and before
    }
    

注意

如果你要生成的应用不以不受支持的平台为目标,则不会发生任何违规行为。 只有在以下情况下,才会发生违规行为:

  • 项目面向属性为不受支持的平台。

  • platformName 包含在默认的 MSBuild <SupportedPlatform> 项组中。

  • platformName 通过手动方式包含在 MSBuild <SupportedPlatform> 项目组中。

    <ItemGroup>
        <SupportedPlatform Include="platformName" />
    </ItemGroup>
    

如何解决冲突

若要解决违规问题,建议确保只在相应的平台上运行时调用特定于平台的 API。 为此,请在生成时使用 #if 和多目标排除代码,或者在运行时有条件地调用代码。 分析器会识别 OperatingSystem 类和 System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform 中的平台保护。

  • 通过使用标准平台临界方法或带有 SupportedOSPlatformGuardAttributeUnsupportedOSPlatformGuardAttribute 注释的自定义临界 API 围绕调用站点,阻止违反。

    // An API supported only on Linux.
    [SupportedOSPlatform("linux")]
    public void LinuxOnlyApi() { }
    
    // API is supported on Windows, iOS from version 14.0, and MacCatalyst from version 14.0.
    [SupportedOSPlatform("windows")]
    [SupportedOSPlatform("ios14.0")] // MacCatalyst is a superset of iOS, therefore it's also supported.
    public void SupportedOnWindowsIos14AndMacCatalyst14() { }
    
    public void Caller()
    {
        LinuxOnlyApi(); // This call site is reachable on all platforms. 'LinuxOnlyApi()' is only supported on: 'linux'.
    
        SupportedOnWindowsIos14AndMacCatalyst14(); // This call site is reachable on all platforms. 'SupportedOnWindowsIos14AndMacCatalyst14()'
                                                   // is only supported on: 'windows', 'ios' 14.0 and later, 'MacCatalyst' 14.0 and later.
    }
    
    [SupportedOSPlatformGuard("windows")]  // The platform guard attributes used
    [SupportedOSPlatformGuard("ios14.0")]
    private readonly bool _isWindowOrIOS14 = OperatingSystem.IsWindows() || OperatingSystem.IsIOSVersionAtLeast(14);
    
    // The warnings are avoided using platform guard methods.
    public void Caller()
    {
        if (OperatingSystem.IsLinux()) // standard guard examples
        {
            LinuxOnlyApi(); // no diagnostic
        }
    
        if (OperatingSystem.IsIOSVersionAtLeast(14))
        {
            SupportedOnWindowsAndIos14(); // no diagnostic
        }
    
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            SupportedOnWindowsAndIos14(); // no diagnostic
        }
    
        if (_isWindowOrMacOS14) // custom guard example
        {
            SupportedOnWindowsAndIos14(); // no diagnostic
        }
    }
    
    // An API not supported on Android but supported on all other platforms.
    [UnsupportedOSPlatform("android")]
    public void DoesNotWorkOnAndroid() { }
    
    // An API was unsupported on Windows until version 10.0.18362.
    // The API is considered supported everywhere else without constraints.
    [UnsupportedOSPlatform("windows")]
    [SupportedOSPlatform("windows10.0.18362")]
    public void StartedWindowsSupportFromCertainVersion();
    
    public void Caller()
    {
        DoesNotWorkOnAndroid(); // This call site is reachable on all platforms.'DoesNotWorkOnAndroid()' is unsupported on: 'android'
    
        StartedWindowsSupportFromCertainVersion(); // This call site is reachable on all platforms. 'StartedWindowsSupportFromCertainVersion()' is unsupported on: 'windows' 10.0.18362 and before.
    }
    
    [UnsupportedOSPlatformGuard("android")] // The platform guard attribute
    bool IsNotAndroid => !OperatingSystem.IsAndroid();
    
    public void Caller()
    {
        if (!OperatingSystem.IsAndroid()) // using standard guard methods
        {
            DoesNotWorkOnAndroid(); // no diagnostic
        }
    
        // Use the && and || logical operators to guard combined attributes.
        if (!OperatingSystem.IsWindows() || OperatingSystem.IsWindowsVersionAtLeast(10, 0, 18362))
        {
            StartedWindowsSupportFromCertainVersion(); // no diagnostic
        }
    
        if (IsNotAndroid) // custom guard example
        {
            DoesNotWorkOnAndroid(); // no diagnostic
        }
    }
    
  • 分析器还会使用 System.Diagnostics.Debug.Assert 来防止不受支持的平台访问代码。 使用 Debug.Assert 时,可以根据需要从发行版本中去除此检查。

    // An API supported only on Linux.
    [SupportedOSPlatform("linux")]
    public void LinuxOnlyApi() { }
    
    public void Caller()
    {
        Debug.Assert(OperatingSystem.IsLinux());
    
        LinuxOnlyApi(); // No diagnostic
    }
    
  • 可以选择将自己的 API 标记为特定于平台,从而有效地将要求转发给调用方。 可以将平台属性应用于以下任何 API:

    • 类型
    • 成员(方法、字段、属性和事件)
    • 程序集
    [SupportedOSPlatform("windows")]
    [SupportedOSPlatform("ios14.0")]
    public void SupportedOnWindowsAndIos14() { }
    
    [SupportedOSPlatform("ios15.0")] // call site version should be equal to or higher than the API version
    public void Caller()
    {
        SupportedOnWindowsAndIos14(); // No diagnostics
    }
    
    [UnsupportedOSPlatform("windows")]
    [SupportedOSPlatform("windows10.0.18362")]
    public void StartedWindowsSupportFromCertainVersion();
    
    [UnsupportedOSPlatform("windows")]
    [SupportedOSPlatform("windows10.0.18362")]
    public void Caller()
    {
        StartedWindowsSupportFromCertainVersion(); // No diagnostics
    }
    
  • 应用程序集或类型级别的属性时,程序集或类型中的所有成员都被视为特定于平台。

    [assembly:SupportedOSPlatform("windows")]
    public namespace ns
    {
        public class Sample
        {
            public void SupportedOnWindows() { }
    
            public void Caller()
            {
                SupportedOnWindows(); // No diagnostic as call site and calling method both windows only
            }
        }
    }
    

何时禁止显示警告

在没有适当平台上下文或不受保护的情况下,不建议引用特定于平台的 API。 但是,你可以使用 #pragmaNoWarn 编译器标志来禁止显示此类诊断,也可以通过在 .editorconfig 文件中设置规则的严重性,将值设置为 none 来实现此目的。

[SupportedOSPlatform("linux")]
public void LinuxOnlyApi() { }

public void Caller()
{
#pragma warning disable CA1416
    LinuxOnlyApi();
#pragma warning restore CA1416
}

配置代码以进行分析

默认情况下,只有当项目面向 .NET 5 或更高版本并且其 AnalysisLevel 为 5 或更高值时,分析器才会启用。 若要为低于 net5.0 的目标框架启用它,可以向项目中的 .editorconfig 文件添加以下键值对:

dotnet_code_quality.enable_platform_analyzer_on_pre_net5_target = true

请参阅