Introducción a las advertencias de AOT

Al publicar la aplicación como de AOT nativo, el proceso de compilación genera todo el código nativo y las estructuras de datos que se necesitan para admitir la aplicación en tiempo de ejecución. Esto es diferente de las implementaciones no nativas, que ejecutan la aplicación a partir de formatos que describen la aplicación en términos abstractos (un programa de una máquina virtual) y que crean representaciones nativas a petición en tiempo de ejecución.

Las representaciones abstractas de elementos de programa no tienen una asignación uno a uno a una representación nativa. Por ejemplo, la descripción abstracta del método List<T>.Add genérico se asigna a cuerpos de método nativos potencialmente infinitos que deben estar especializados para el objeto T especificado (por ejemplo, List<int>.Add y List<double>.Add).

Dado que la relación de código abstracto con código nativo no es de uno a uno, el proceso de compilación debe crear una lista completa de cuerpos de código nativo y estructuras de datos en tiempo de compilación. Crear esta lista en tiempo de compilación puede ser difícil con algunas de las API de .NET. Si la API se usa de forma que no se haya previsto en tiempo de compilación, se producirá una excepción en tiempo de ejecución.

Para evitar cambios de comportamiento al implementar como AOT nativo, el SDK de .NET proporciona un análisis estático de la compatibilidad de AOT mediante "advertencias de AOT". El recortador genera advertencias de AOT cuando encuentra código que puede no ser compatible con AOT. El código que no es compatible con AOT puede producir cambios de comportamiento, o incluso bloqueos, en una aplicación que se ha creado como de AOT nativo. Lo ideal es que ninguna de las aplicaciones que usan AOT nativo muestren advertencias de AOT. Si existe alguna advertencia de AOT, asegúrese de que no hay ningún cambio de comportamiento; para ello, compruebe minuciosamente la aplicación después de crearla como de AOT nativo.

Ejemplos de advertencias de AOT

En la mayor parte del código de C# resulta fácil saber qué código nativo se debe generar. El compilador nativo puede recorrer los cuerpos del método y detectar el código nativo y las estructuras de datos a los que se accede. Desafortunadamente, algunas características, como la reflexión, presentan un problema importante. Observe el código siguiente:

Type t = typeof(int);
while (true)
{
    t = typeof(GenericType<>).MakeGenericType(t);
    Console.WriteLine(Activator.CreateInstance(t));
}

struct GenericType<T> { }

Aunque el programa anterior no es muy útil, constituye un caso extremo que requiere crear un número infinito de tipos genéricos al crear la aplicación como de AOT nativo. Sin AOT nativo, el programa se ejecutaría hasta agotar la memoria. Con AOT nativo, ni siquiera podríamos crear la aplicación si tuviéramos que generar todos los tipos necesarios (un número infinito de ellos).

En este caso, la compilación con AOT nativo emite la siguiente advertencia en la línea MakeGenericType:

AOT analysis warning IL3050: Program.<Main>$(String[]): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.

En tiempo de ejecución, la aplicación sí producirá una excepción desde la llamada MakeGenericType.

Reacción ante advertencias de AOT

Las advertencias de AOT están diseñadas para aportar predictibilidad a las compilaciones con AOT nativo. La mayoría de las advertencias de AOT tienen que ver con una posible excepción en tiempo de ejecución en situaciones en las que no se generado código nativo para admitir el escenario. La categoría más amplia es RequiresDynamicCodeAttribute.

RequiresDynamicCode

RequiresDynamicCodeAttribute es simple y amplio: es un atributo que indica que el miembro se ha anotado como incompatible con AOT. Esta anotación significa que el miembro puede usar la reflexión u otro mecanismo para crear código nativo en tiempo de ejecución. Este atributo se usa cuando el código no es compatible con AOT en esencia, o cuando la dependencia nativa es demasiado compleja para predecir estáticamente en tiempo de compilación. Esto suele ser así en métodos que usan la API Type.MakeGenericType, la emisión de reflexión u otras tecnologías de generación de código en un entorno de ejecución. El código siguiente muestra un ejemplo.

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

void TestMethod()
{
    // IL3050: Using method 'MethodWithReflectionEmit' which has 'RequiresDynamicCodeAttribute'
    // can break functionality when AOT compiling. Use 'MethodFriendlyToAot' instead.
    MethodWithReflectionEmit();
}

No hay muchas soluciones alternativas para RequiresDynamicCode. La mejor solución consiste en no llamar al método bajo ningún concepto al crear aplicaciones con AOT nativo, sino usar otro sistema que sea compatible con AOT. Si está escribiendo una biblioteca y no está en su mano llamar o no al método, también puede añadir RequiresDynamicCode a su propio método. Esto anotará el método como no compatible con AOT. Agregar RequiresDynamicCode silenciará todas las advertencias de AOT en el método anotado, pero producirá una advertencia cada vez que otra persona lo llame. Por esta razón, la mayoría de las veces los autores de bibliotecas encuentran útil meter la advertencia "en una burbuja" en una API pública.

Si puede determinar de alguna manera que la llamada es segura y que todo el código nativo va a estar disponible en tiempo de ejecución, también puede suprimir la advertencia mediante UnconditionalSuppressMessageAttribute. Por ejemplo:

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

[UnconditionalSuppressMessage("Aot", "IL3050:RequiresDynamicCode",
    Justification = "The unfriendly method is not reachable with AOT")]
void TestMethod()
{
    If (RuntimeFeature.IsDynamicCodeSupported)
        MethodWithReflectionEmit(); // warning suppressed
}

UnconditionalSuppressMessage es como SuppressMessage, pero lo pueden ver publish y otras herramientas posteriores a la compilación. Las directivas SuppressMessage y #pragma solo están presentes en el origen, por lo que no se pueden usar para silenciar las advertencias de la compilación.

Precaución

Tenga cuidado al suprimir advertencias de AOT. La llamada podría ser compatible con AOT ahora, pero a medida que actualiza el código, esto podría cambiar y podría olvidarse de revisar todas las supresiones.