Introduzione agli avvisi AOT

Quando si pubblica l'applicazione come Native AOT, il processo di compilazione produce tutto il codice nativo e le strutture di dati necessarie per supportare l'applicazione in fase di esecuzione. Ciò è diverso dalle distribuzioni non native, che eseguono l'applicazione dai formati che descrivono l'applicazione in termini astratti (un programma per una macchina virtuale) e creano rappresentazioni native su richiesta in fase di esecuzione.

Le rappresentazioni astratte delle parti del programma non hanno un mapping uno-a-uno con la rappresentazione nativa. Ad esempio, la descrizione astratta del metodo List<T>.Add generico è mappata a corpi di metodi nativi potenzialmente infiniti che devono essere specializzati per il dato T (ad esempio, List<int>.Add e List<double>.Add).

Poiché la relazione tra codice astratto e codice nativo non è uno-a-uno, il processo di compilazione deve creare un elenco completo di corpi di codice nativo e strutture di dati in fase di compilazione. Per alcune API .NET può essere difficile creare questo elenco in fase di compilazione. Se l'API viene usata in modo non previsto in fase di compilazione, verrà generata un'eccezione in fase di esecuzione.

Per evitare modifiche di comportamento durante la distribuzione come Native AOT, l’SDK .NET fornisce un'analisi statica della compatibilità AOT tramite “avvisi AOT”. Gli avvisi AOT vengono generati quando la compilazione trova codice che potrebbe non essere compatibile con AOT. Il codice non compatibile con AOT può produrre modifiche funzionali o persino arresti anomali in un'applicazione dopo che è stato compilato come Native AOT. Idealmente, nessuna applicazione che usa Native AOT dovrebbe avere avvisi AOT. Se sono presenti avvisi AOT, assicurati che non ci siano modifiche funzionali testando accuratamente la tua app dopo la compilazione come Native AOT.

Esempi di avvisi AOT

Per la maggior parte del codice C#, è semplice determinare quale codice nativo deve essere generato. Il compilatore nativo può esaminare i corpi dei metodi e individuare il codice nativo e le strutture di dati a cui si accede. Sfortunatamente, alcune funzionalità, come la reflection, presentano un problema significativo. Osservare il codice seguente:

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

struct GenericType<T> { }

Anche se il programma precedente non è molto utile, rappresenta un caso estremo che richiede la creazione di un numero infinito di tipi generici durante la compilazione dell'applicazione come Native AOT. Senza Native AOT, il programma verrebbe eseguito fino a quando non esaurisce la memoria. Con Native AOT, non saremmo in grado di compilarlo nemmeno se fosse necessario generare tutti i tipi necessari (un numero infinito).

In questo caso, la compilazione Native AOT genera l'avviso seguente sulla riga 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.

In fase di esecuzione, l'applicazione genererà effettivamente un'eccezione dalla chiamata MakeGenericType.

Rispondere agli avvisi AOT

Gli avvisi AOT hanno lo scopo di rendere prevedibili le compilazioni Native AOT. La maggior parte degli avvisi AOT riguarda possibili eccezioni in fase di esecuzione in situazioni in cui il codice nativo non è stato generato per supportare lo scenario. La categoria più ampia è RequiresDynamicCodeAttribute.

RequiresDynamicCode

RequiresDynamicCodeAttribute è semplice e ampio: si tratta di un attributo che indica che il membro è stato annotato come incompatibile con AOT. Questa annotazione indica che il membro potrebbe usare reflection o un altro meccanismo per creare un nuovo codice nativo in fase di esecuzione. Questo attributo viene usato quando il codice è fondamentalmente non compatibile con AOT o quando la dipendenza nativa è troppo complessa da essere prevista in modo statico in fase di compilazione. Questo vale spesso per i metodi che usano l'API Type.MakeGenericType, la reflection emit o altre tecnologie di generazione di codice in fase di esecuzione. Il seguente codice illustra un esempio.

[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();
}

Non esistono molte soluzioni alternative per RequiresDynamicCode. La soluzione migliore consiste nell'evitare di chiamare il metodo quando si compila come Native AOT e usare qualcos'altro compatibile con AOT. Se stai scrivendo una libreria e non sai se chiamare o meno il metodo, puoi anche aggiungere RequiresDynamicCode al tuo metodo. In questo modo il metodo verrà annotato come non compatibile con AOT. L'aggiunta di RequiresDynamicCode disattiva tutti gli avvisi AOT nel metodo annotato, ma genererà un avviso ogni volta che un qualcun altro lo chiama. Per questo motivo, è utile soprattutto per gli autori di librerie "creare un'anteprima" dell'avviso a un'API pubblica.

Se in qualche modo è possibile determinare che la chiamata è sicura e che tutto il codice nativo sarà disponibile in fase di esecuzione, puoi anche eliminare l'avviso usando UnconditionalSuppressMessageAttribute. Ad esempio:

[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 è come SuppressMessage ma può essere visto da publish e da altri strumenti di post-compilazione. Le direttive SuppressMessage e #pragma sono presenti solo nell'origine, quindi non possono essere usate per disattivare gli avvisi della compilazione.

Attenzione

Presta attenzione quando elimini gli avvisi AOT. La chiamata ora potrebbe essere compatibile con AOT, ma con la modifica del codice, la situazione potrebbe cambiare e ci si potrebbe dimenticare di esaminare tutte le eliminazioni.