CA1416: Validación de la compatibilidad con las plataformas

Propiedad Value
Identificador de la regla CA1416
Título Validación de la compatibilidad con las plataformas
Categoría Interoperabilidad
La corrección es problemática o no problemática Poco problemático
Habilitado de forma predeterminada en .NET 8 Como advertencia

Causa

Las infracciones se detectan si se usa una API específica de la plataforma en el contexto de una plataforma diferente o si no se comprueba la plataforma (independiente de la plataforma). También se muestran las infracciones si se usa una API incompatible con la plataforma de destino del proyecto.

Esta regla está habilitada de forma predeterminada, solo para los proyectos que tienen como destino .NET 5 o una versión posterior. Sin embargo, puede habilitar para los proyectos que tienen como destino otras plataformas.

Descripción de la regla

En .NET 5 se han agregado nuevos atributos, SupportedOSPlatformAttribute y UnsupportedOSPlatformAttribute, para anotar las API específicas de la plataforma. Se puede crear una instancia de ambos atributos con o sin números de versión como parte del nombre de la plataforma. También se pueden aplicar varias veces con otras plataformas.

  • Se considera que una API sin anotar funciona en todas las plataformas de sistema operativo (SO).
  • Una API marcada con [SupportedOSPlatform("platformName")] solo se considera portable a las plataformas del sistema operativo especificado. Si la plataforma es un subconjunto de otra plataforma, el atributo implica que también se admite esa plataforma.
  • Una API marcada con [UnsupportedOSPlatform("platformName")] se considera incompatible con las plataformas del sistema operativo especificado. Si la plataforma es un subconjunto de otra plataforma, el atributo implica que esa plataforma tampoco se admite.

Puede combinar los atributos [SupportedOSPlatform] y [UnsupportedOSPlatform] en una sola API. En este caso, se aplican las siguientes reglas:

  • Lista de permitidos. Si la versión más antigua de cada plataforma de sistema operativo es un atributo [SupportedOSPlatform], se considera que la API solo es compatible con las plataformas de la lista y no es compatible con todas las demás plataformas. La lista puede tener un atributo [UnsupportedOSPlatform] con la misma plataforma, pero solo con una versión posterior, lo que indica que la API se ha eliminado de esa versión.
  • Lista de denegación. Si la versión más antigua de cada plataforma de sistema operativo es un atributo [UnsupportedOSPlatform], se considera que la API no es compatible solo con las plataformas de la lista y es compatible con todas las demás plataformas. La lista puede tener un atributo [SupportedOSPlatform] con la misma plataforma, pero solo con una versión posterior, lo que indica que la API se admite desde esa versión.
  • Lista de incoherentes: Si la versión mínima para algunas plataformas es [SupportedOSPlatform], pero es [UnsupportedOSPlatform] para otras, esta combinación se considera incoherente. Se omiten algunas anotaciones de la API. En el futuro, es posible que se introduzca un analizador que genere una advertencia en caso de incoherencia.

Si accede a una API anotada con estos atributos desde el contexto de otra plataforma, puede ver infracciones de CA1416.

Plataformas de destino de TFM

El analizador no comprueba las plataformas de destino del moniker de la plataforma de destino (TFM) desde las propiedades de MSBuild, como <TargetFramework> o <TargetFrameworks>. Si el TFM tiene una plataforma de destino, .NET SDK inserta un atributo SupportedOSPlatform con el nombre de la plataforma de destino en el archivo AssemblyInfo.cs, que el analizador consume. Por ejemplo, si el TFM es net5.0-windows10.0.19041, SDK inserta el atributo [assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.19041")] en el archivo AssemblyInfo.cs y se considera que todo el ensamblado es solo para Windows. Por tanto, la llamada a las API exclusivas de Windows con la versión 7.0 o inferior no provocaría ninguna advertencia en el proyecto.

Nota

Si la generación de archivos AssemblyInfo.cs está deshabilitada para el proyecto (es decir, la propiedad <GenerateAssemblyInfo> está establecida en false), SDK no puede agregar el atributo SupportedOSPlatform de nivel de ensamblado necesario. En este caso, podría ver advertencias sobre el uso de API específicas de la plataforma, aunque tenga como destino esa plataforma. Para resolver las advertencias, habilite la generación de archivos AssemblyInfo.cs o agregue el atributo manualmente en el proyecto.

Infracciones

  • Si accede a una API que solo es compatible con una plataforma especificada ([SupportedOSPlatform("platformName")]) con código accesible en otras plataformas, verá la siguiente infracción: "API" es compatible con "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.
    }
    

    Nota

    Solo se produce una infracción si el proyecto no tiene como destino la plataforma compatible (net5.0-differentPlatform). Esto también se aplica a los proyectos con destinos múltiples. No se produce ninguna infracción si el proyecto tiene como destino la plataforma especificada (net5.0-platformName) y la generación de archivos AssemblyInfo.cs está habilitada para el proyecto.

  • El acceso a una API que tiene el atributo [UnsupportedOSPlatform("platformName")] en un contexto que tiene como destino la plataforma no compatible podría producir una infracción: "API" no es compatible con "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
    }
    

Nota

Si va a crear una aplicación que no tiene como destino la plataforma no compatible, no obtendrá ninguna infracción. Solo se produce una infracción en los casos siguientes:

  • El proyecto tiene como destino la plataforma que se atribuye como no admitida.

  • platformName se incluye en el grupo de elementos <SupportedPlatform> de MSBuild predeterminado.

  • platformName se incluye manualmente en el grupo de elementos de MSBuild <SupportedPlatform>.

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

Cómo corregir infracciones

La manera recomendada de tratar las infracciones es asegurarse de que solo se llama a las API específicas de la plataforma cuando se ejecuta en una plataforma adecuada. Puede conseguirlo excluyendo el código en tiempo de compilación mediante #if y con compatibilidad con múltiples versiones, o llamando condicionalmente al código en tiempo de ejecución. El analizador reconoce las restricciones de la plataforma en la clase OperatingSystem y en System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform.

  • Para suprimir las infracciones, el sitio de llamada se rodea con los métodos estándar de protección de plataforma o las API de protección personalizada anotadas con SupportedOSPlatformGuardAttribute o 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
        }
    }
    
  • El analizador también respeta System.Diagnostics.Debug.Assert como un medio para evitar que el código se alcance en plataformas no compatibles. El uso de Debug.Assert permite recortar la comprobación de las compilaciones de versión, si así se desea.

    // An API supported only on Linux.
    [SupportedOSPlatform("linux")]
    public void LinuxOnlyApi() { }
    
    public void Caller()
    {
        Debug.Assert(OperatingSystem.IsLinux());
    
        LinuxOnlyApi(); // No diagnostic
    }
    
  • Puede elegir marcar sus propias API como específicas de la plataforma y reenviar los requisitos a los autores de llamadas de forma eficaz. Puede aplicar los atributos de plataforma a cualquiera de las siguientes API:

    • Tipos
    • Miembros (métodos, campos, propiedades y eventos)
    • Ensamblados
    [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
    }
    
  • Cuando se aplica un atributo de nivel de tipo o ensamblado, se considera que todos los miembros del ensamblado o tipo son específicos de la 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
            }
        }
    }
    

Cuándo suprimir las advertencias

No se recomienda hacer referencia a las API específicas de la plataforma sin un contexto de plataforma o una restricción adecuados. Puedes suprimir estos diagnósticos usando #pragma o la marca del compilador NoWarn, o bien puedes establecer la gravedad de la regla en none un archivo .editorconfig.

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

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

Configuración del código para analizar

El analizador está habilitado de manera predeterminada solo para los proyectos que tienen como destino .NET 5 o posterior, y tienen un valor de AnalysisLevel de 5 o superior. Puede habilitarlo para las plataformas de destino inferiores a net5.0 agregando el siguiente par clave-valor a un archivo .editorconfig en el proyecto:

dotnet_code_quality.enable_platform_analyzer_on_pre_net5_target = true

Consulte también