Επεξεργασία

Κοινή χρήση μέσω


CA1416: Validate platform compatibility

Property Value
Rule ID CA1416
Title Validate platform compatibility
Category Interoperability
Fix is breaking or non-breaking Non-breaking
Enabled by default in .NET 9 As warning

Cause

Violations are reported if a platform-specific API is used in the context of a different platform or if the platform isn't verified (platform-neutral). Violations are also reported if an API that's not supported for the target platform of the project is used.

This rule is enabled by default only for projects that target .NET 5 or later. However, you can enable it for projects that target other frameworks.

Rule description

.NET 5 added new attributes, SupportedOSPlatformAttribute and UnsupportedOSPlatformAttribute, to annotate platform-specific APIs. Both attributes can be instantiated with or without version numbers as part of the platform name. They can also be applied multiple times with different platforms.

  • An unannotated API is considered to work on all operating system (OS) platforms.
  • An API marked with [SupportedOSPlatform("platformName")] is considered to be portable to the specified OS platforms only. If the platform is a subset of another platform, the attribute implies that that platform is also supported.
  • An API marked with [UnsupportedOSPlatform("platformName")] is considered to be unsupported on the specified OS platforms. If the platform is a subset of another platform, the attribute implies that that platform is also unsupported.

You can combine [SupportedOSPlatform] and [UnsupportedOSPlatform] attributes on a single API. In this case, the following rules apply:

  • Allow list. If the lowest version for each OS platform is a [SupportedOSPlatform] attribute, the API is considered to only be supported by the listed platforms and unsupported by all other platforms. The list can have an [UnsupportedOSPlatform] attribute with the same platform, but only with a higher version, which denotes that the API is removed from that version.
  • Deny list. If the lowest version for each OS platform is an [UnsupportedOSPlatform] attribute, then the API is considered to only be unsupported by the listed platforms and supported by all other platforms. The list can have a [SupportedOSPlatform] attribute with the same platform, but only with a higher version, which denotes that the API is supported since that version.
  • Inconsistent list. If the lowest version for some platforms is [SupportedOSPlatform] but [UnsupportedOSPlatform] for other platforms, this combination is considered inconsistent. Some annotations on the API are ignored. In the future, we may introduce an analyzer that produces a warning in case of inconsistency.

If you access an API annotated with these attributes from the context of a different platform, you can see CA1416 violations.

TFM target platforms

The analyzer does not check target framework moniker (TFM) target platforms from MSBuild properties, such as <TargetFramework> or <TargetFrameworks>. If the TFM has a target platform, the .NET SDK injects a SupportedOSPlatform attribute with the targeted platform name in the AssemblyInfo.cs file, which is consumed by the analyzer. For example, if the TFM is net5.0-windows10.0.19041, the SDK injects the [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.19041")] attribute into the AssemblyInfo.cs file, and the entire assembly is considered to be Windows only. Therefore, calling Windows-only APIs versioned with 7.0 or below would not cause any warnings in the project.

Note

If the AssemblyInfo.cs file generation is disabled for the project (that is, the <GenerateAssemblyInfo> property is set to false), the required assembly level SupportedOSPlatform attribute can't be added by the SDK. In this case, you could see warnings for a platform-specific APIs usage even if you're targeting that platform. To resolve the warnings, enable the AssemblyInfo.cs file generation or add the attribute manually in your project.

Violations

  • If you access an API that's supported only on a specified platform ([SupportedOSPlatform("platformName")]) from code reachable on other platforms, you'll see the following violation: 'API' is supported on 'platformName'.

    // 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.
    }
    

    Note

    A violation only occurs if the project does not target the supported platform (net5.0-differentPlatform). This also applies to multi-targeted projects. No violation occurs if the project targets the specified platform (net5.0-platformName) and the AssemblyInfo.cs file generation is enabled for the project.

  • Accessing an API that's attributed with [UnsupportedOSPlatform("platformName")] from a context that targets the unsupported platform could produce a violation: 'API' is unsupported on 'platformName'.

    // 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
    }
    

Note

If you're building an app that doesn't target the unsupported platform, you won't get any violations. A violation only occurs in the following cases:

  • The project targets the platform that's attributed as unsupported.

  • The platformName is included in the default MSBuild <SupportedPlatform> items group.

  • platformName is manually included in the MSBuild <SupportedPlatform> items group.

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

How to fix violations

The recommended way to deal with violations is to make sure you only call platform-specific APIs when running on an appropriate platform. You can achieve this by excluding the code at build time using #if and multi-targeting, or by conditionally calling the code at run time. The analyzer recognizes the platform guards in the OperatingSystem class and System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform.

  • Suppress violations by surrounding the call site with the standard platform guard methods or custom guard APIs annotated with SupportedOSPlatformGuardAttribute or UnsupportedOSPlatformGuardAttribute.

    // 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
        }
    }
    
  • The analyzer also respects System.Diagnostics.Debug.Assert as a means for preventing the code from being reached on unsupported platforms. Using Debug.Assert allows the check to be trimmed out of release builds, if desired.

    // An API supported only on Linux.
    [SupportedOSPlatform("linux")]
    public void LinuxOnlyApi() { }
    
    public void Caller()
    {
        Debug.Assert(OperatingSystem.IsLinux());
    
        LinuxOnlyApi(); // No diagnostic
    }
    
  • You can choose to mark your own APIs as being platform-specific, effectively forwarding the requirements to your callers. You can apply platform attributes to any of the following APIs:

    • Types
    • Members (methods, fields, properties, and events)
    • Assemblies
    [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
    }
    
  • When an assembly-level or type-level attribute is applied, all members within the assembly or type are considered to be platform specific.

    [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
            }
        }
    }
    

When to suppress warnings

Referencing platform-specific APIs without a proper platform context or guard is not recommended. However, you can suppress these diagnostics using #pragma or the NoWarn compiler flag, or by setting the rule's severity to none in an .editorconfig file.

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

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

Configure code to analyze

The analyzer is enabled by default only for projects that target .NET 5 or later and have an AnalysisLevel of 5 or higher. You can enable it for target frameworks lower than net5.0 by adding the following key-value pair to an .editorconfig file in your project:

dotnet_code_quality.enable_platform_analyzer_on_pre_net5_target = true

See also