CA1416: validar a compatibilidade da plataforma

Property Valor
ID da regra CA1416
Título validar a compatibilidade da plataforma
Categoria Interoperabilidade
Correção interruptiva ou sem interrupção Sem interrupção
Habilitado por padrão no .NET 8 Como aviso

Causa

As violações são relatadas se uma API específica da plataforma for usada no contexto de uma plataforma diferente ou se a plataforma não for verificada (com neutralidade de plataforma). As violações também são relatadas se uma API incompatível com a plataforma de destino do projeto for usada.

Essa regra está habilitada por padrão somente para projetos direcionados ao .NET 5 ou posterior. No entanto, você pode habilitá-la para projetos direcionados a outras estruturas.

Descrição da regra

O .NET 5 adicionou novos atributos, SupportedOSPlatformAttribute e UnsupportedOSPlatformAttribute, para anotar as APIs específicas da plataforma. Ambos os atributos podem ser instanciados com ou sem números de versão como parte do nome da plataforma. Eles também podem ser aplicados várias vezes com plataformas diferentes.

  • Considera-se que uma API sem anotação funcione em todas as plataformas do SO (sistema operacional).
  • Uma API marcada com [SupportedOSPlatform("platformName")] é considerada portátil apenas para as plataformas especificadas do SO. Se a plataforma for um subconjunto de outra plataforma, o atributo implicará que essa plataforma também é compatível.
  • Uma API marcada com [UnsupportedOSPlatform("platformName")] é considerada incompatível nas plataformas especificadas do SO. Se a plataforma for um subconjunto de outra plataforma, o atributo implicará que essa plataforma também é incompatível.

Você pode combinar os atributos [SupportedOSPlatform] e [UnsupportedOSPlatform] em uma única API. Nesse caso, as seguintes regras se aplicam:

  • Lista de permissões. Se a versão mais antiga de cada plataforma do SO for um atributo [SupportedOSPlatform], a API será considerada compatível apenas com as plataformas listadas e incompatível com todas as outras plataformas. A lista pode ter um atributo [UnsupportedOSPlatform] com a mesma plataforma, mas apenas com uma versão posterior, o que indica que a API foi removida dessa versão.
  • Lista de negações. Se a versão mais antiga de cada plataforma do SO for um atributo [UnsupportedOSPlatform], a API será considerada incompatível apenas com as plataformas listadas e compatível com todas as outras plataformas. A lista pode ter um atributo [SupportedOSPlatform] com a mesma plataforma, mas apenas com uma versão posterior, o que indica que a API é compatível a partir dessa versão.
  • Lista de inconsistências. Se a versão mais antiga para algumas plataformas for [SupportedOSPlatform], mas [UnsupportedOSPlatform] para outras plataformas, essa combinação será considerada inconsistente. Algumas anotações na API são ignoradas. Futuramente, podemos lançar um analisador que gere um aviso em caso de inconsistência.

Se você acessar uma API com anotação e esses atributos no contexto de uma plataforma diferente, poderá ver violações do CA1416.

Plataformas de destino do TFM

O analisador não verifica as plataformas de destino do TFM (Moniker da Estrutura de Destino) nas propriedades do MSBuild, como <TargetFramework> ou <TargetFrameworks>. Se o TFM tiver uma plataforma de destino, o SDK do .NET injetará um atributo SupportedOSPlatform com o nome da plataforma de destino no arquivo AssemblyInfo.cs, o qual será consumido pelo analisador. Por exemplo, se o TFM for net5.0-windows10.0.19041, o SDK injetará o atributo [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.19041")] no arquivo AssemblyInfo.cs e todo o assembly será considerado somente windows. Portanto, a chamada a APIs somente Windows na versão 7.0 ou anterior não geraria avisos no projeto.

Observação

Se a geração do arquivo AssemblyInfo.cs estiver desabilitada para o projeto (ou seja, a propriedade <GenerateAssemblyInfo> foi definida como false), o atributo SupportedOSPlatform de nível de assembly necessário não poderá ser adicionado pelo SDK. Nesse caso, avisos podem ser exibidos para o uso de APIs específicas da plataforma, mesmo se você estiver direcionando para essa plataforma. Para resolver os avisos, habilite a geração do arquivo AssemblyInfo.cs ou adicione o atributo manualmente no projeto.

Violações

  • Se você acessar uma API compatível somente em uma plataforma especificada ([SupportedOSPlatform("platformName")]) no código acessível em outras plataformas, verá a seguinte violação: há suporte para 'API' em '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.
    }
    

    Observação

    Ocorrerá uma violação somente se o projeto não for direcionado para a plataforma compatível (net5.0-differentPlatform). Isso também se aplica a projetos de vários destinos. Não ocorrerá violação se o projeto for direcionado para a plataforma especificada (net5.0-platformName) e a geração do arquivo AssemblyInfo.cs estiver habilitada para o projeto.

  • Acessar uma API atribuída a [UnsupportedOSPlatform("platformName")] em um contexto direcionado para a plataforma incompatível pode gerar uma violação: não há suporte para 'API' em '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
    }
    

Observação

Se você estiver compilando um aplicativo que não foi direcionado para a plataforma incompatível, não ocorrerá uma violação. Ocorrerá uma violação somente nos seguintes casos:

  • O projeto tem como alvo a plataforma que é atribuída como não suportada.

  • O platformName está incluído no grupo de itens padrão do MSBuild<SupportedPlatform>.

  • platformName é incluído manualmente no grupo de itens <SupportedPlatform> do MSBuild.

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

Como corrigir violações

A maneira recomendada de lidar com violações é chamar somente as APIs específicas da plataforma durante a execução em uma plataforma adequada. Você pode fazer isso excluindo o código no tempo de compilação, usando #if e vários destinos ou por meio de uma chamada condicional ao código no tempo de execução. O analisador reconhece as proteções da plataforma na classe OperatingSystem e em System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform.

  • Suprima as violações contornando o site de chamada com os métodos de proteção de plataforma padrão ou APIs de proteção personalizadas com anotação de SupportedOSPlatformGuardAttribute ou 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
        }
    }
    
  • O analisador também atender a System.Diagnostics.Debug.Assert como meio de impedir que o código seja acessado em plataformas incompatíveis. O uso de Debug.Assert permite que a verificação seja cortada as compilações da versão, se desejado.

    // An API supported only on Linux.
    [SupportedOSPlatform("linux")]
    public void LinuxOnlyApi() { }
    
    public void Caller()
    {
        Debug.Assert(OperatingSystem.IsLinux());
    
        LinuxOnlyApi(); // No diagnostic
    }
    
  • Você pode optar por marcar suas próprias APIs como se fossem específicas da plataforma, encaminhando de fato os requisitos para os chamadores. Você pode aplicar os atributos de plataforma a qualquer uma das seguintes APIs:

    • Tipos
    • Membros (métodos, campos, propriedades e eventos)
    • 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
    }
    
  • Quando um atributo no nível do assembly ou no nível do tipo é aplicado, todos os membros do assembly ou tipo são considerados específicos da plataforma.

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

Quando suprimir avisos

Não é recomendável referenciar APIs específicas da plataforma sem o devido contexto ou proteção de plataforma. No entanto, você pode suprimir esses diagnósticos usando #pragma ou o sinalizador do compilador NoWarn ou definindo a gravidade da regra como none em um arquivo .editorconfig.

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

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

Configurar código para analisar

O analisador está habilitado por padrão apenas para projetos direcionados para o .NET 5 ou posterior e com um AnalysisLevel de 5 ou superior. Você pode habilitá-lo para estruturas de destino menores do que net5.0, adicionando o seguinte par de chave-valor a um arquivo .editorconfig no projeto:

dotnet_code_quality.enable_platform_analyzer_on_pre_net5_target = true

Confira também