CA1416: проверка совместимости платформы

Свойство Значение
Идентификатор правила CA1416
Заголовок Проверка совместимости платформ
Категория Совместимость
Исправление является критическим или не критическим Не критическое
Включен по умолчанию в .NET 8 Как предупреждение

Причина

Если API конкретной платформы используется в контексте другой платформы или эту платформу не удается подтвердить (API независим от платформы), регистрируется нарушение. Также нарушением считается использование API, не поддерживаемого для целевой платформы проекта.

По умолчанию это правило включено только для проектов, нацеленных на .NET 5 или более поздней версии. Но вы можете включить его для проектов, ориентированных на другие платформы.

Описание правила

.NET 5 добавил новые атрибуты SupportedOSPlatformAttribute , а UnsupportedOSPlatformAttributeтакже для анотации api для конкретной платформы. Экземпляры обоих атрибутов могут быть созданы с номерами версий в составе имени платформы или без них. Они могут применяться несколько раз с разными платформами.

  • API без пометок считается подходящим для всех платформ операционной системы (ОС).
  • Считается, что API с меткой [SupportedOSPlatform("platformName")] можно переносить только на указанные платформы ОС. Если платформа является подмножеством другой платформы, то атрибут предполагает, что эта платформа также поддерживается.
  • Считается, что API с меткой [UnsupportedOSPlatform("platformName")] не будет поддерживаться на указанных платформах ОС. Если платформа является подмножеством другой платформы, то атрибут предполагает, что эта платформа также не поддерживается.

Можно комбинировать атрибуты [SupportedOSPlatform] и [UnsupportedOSPlatform] в одном API. В этом случае применяются следующие правила:

  • Список разрешений. Если самая ранняя версия для каждой платформы ОС задана атрибутом [SupportedOSPlatform], API считается таким, который поддерживается только указанными платформами и не поддерживается всеми другими. Этот список может содержать атрибут [UnsupportedOSPlatform] с такой же платформой, но более поздней версии, что обозначает отмену поддержки API начиная с этой версии.
  • Список запретов. Если самая ранняя версия для каждой платформы ОС задана атрибутом [UnsupportedOSPlatform], API считается таким, который не поддерживается только указанными платформами и поддерживается всеми другими. Этот список может содержать атрибут [SupportedOSPlatform] с такой же платформой, но более поздней версии, что обозначает, что API поддерживается начиная с этой версии.
  • Список "Несогласованные". Если самая ранняя версия для одних платформ — [SupportedOSPlatform], а для других — [UnsupportedOSPlatform], такая комбинация считается несогласованной. Некоторые заметки в API игнорируются. В будущем мы можем добавить анализатор, который выдает предупреждение в случае несогласованности.

Если вы обращаетесь к API-интерфейсу, снабженному этими атрибутами, из контекста другой платформы, возникнут нарушения CA1416.

Целевые платформы TFM

Анализатор не проверяет целевые платформы моникера целевой платформы (TFM) в свойствах MSBuild, таких как <TargetFramework> или <TargetFrameworks>. Если TFM имеет целевую платформу, пакет SDK для .NET внедряет SupportedOSPlatform атрибут с именем целевой платформы в файл AssemblyInfo.cs , который используется анализатором. Например, если TFM является net5.0-windows10.0.19041, пакет SDK внедряет [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.19041")] атрибут в файл AssemblyInfo.cs , а вся сборка считается только Windows. Поэтому при вызове API-интерфейсов только для Windows с версией 7.0 или ниже никаких предупреждений в проекте не возникает.

Примечание.

Если для проекта отключено создание файла AssemblyInfo.cs<GenerateAssemblyInfo>. е. свойство имеет значениеfalse), обязательный атрибут уровня SupportedOSPlatform сборки не может быть добавлен пакетом SDK. В этом случае при использовании API для конкретной платформы могут возникать предупреждения, даже если эта платформа является целевой. Для устранения этих предупреждений активируйте создание файла AssemblyInfo.cs или добавьте в проект атрибут вручную.

Нарушения

  • При доступе к API, который поддерживается только для указанной платформы ([SupportedOSPlatform("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 включено для проекта.

  • При доступе к API с атрибутами [UnsupportedOSPlatform("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 вручную включен в группу элементов <SupportedPlatform> MSBuild.

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

Устранение нарушений

При возникновении таких нарушений мы рекомендуем сделать так, чтобы при работе на каждой платформе вызывались только API, предназначенные для этой платформы. Для этого вы можете исключать фрагменты кода во время сборки, используя #if и нацеливание на несколько платформ, или через условный вызов кода во время выполнения. Анализатор распознает условия платформ в классе OperatingSystem и System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform.

  • Чтобы скрыть нарушения, используйте вокруг места вызова стандартные платформенные методы условий или специализированные API условий, помеченные с помощью SupportedOSPlatformGuardAttribute или 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
        }
    }
    
  • Анализатор также признает 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, предназначенные для конкретной платформы, без правильного применения контекста или условий платформы. Однако вы можете отключить эти диагностика с помощью #pragma флага компилятора NoWarn или установить серьезность none правила в файле editorconfig.

[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

См. также