Compartilhar via


Introduction aos avisos de AOT

Ao publicar seu aplicativo como AOT Nativo, o processo de build produz todas as estruturas de dados e código nativo necessárias para dar suporte ao aplicativo em tempo de execução. Isso é diferente das implantações não nativas, que executam o aplicativo de formatos que descrevem o aplicativo em termos abstratos (um programa para uma máquina virtual) e criam representações nativas sob demanda em tempo de execução.

As representações abstratas de partes do programa não têm um mapeamento um para um para a representação nativa. Por exemplo, a descrição abstrata do método genérico List<T>.Add é mapeada para corpos de método nativo potencialmente infinitos que precisam ser especializados para o determinado T (por exemplo, List<int>.Add e List<double>.Add).

Como a relação do código abstrato com o código nativo não é um para um, o processo de build precisa criar uma lista completa de corpos de código nativos e estruturas de dados no momento do build. Pode ser difícil criar essa lista em tempo de build para algumas das APIs do .NET. Se a API for usada de uma forma que não foi prevista no tempo do build, uma exceção será gerada em tempo de execução.

Para evitar alterações no comportamento ao implantar como AOT Nativo, o SDK do .NET fornece análise estática da compatibilidade de AOT por meio de "avisos de AOT". Os avisos de AOT são produzidos quando o build encontra o código que pode não ser compatível com AOT. O código que não é compatível com o AOT pode produzir alterações comportamentais ou até mesmo falhas em um aplicativo após ser compilado como AOT Nativo. O ideal é que todos os aplicativos que usam AOT Nativo não tenham avisos de AOT. Se houver avisos de AOT, verifique se não há alterações de comportamento testando completamente seu aplicativo após a compilação como AOT Nativo.

Exemplos de avisos de AOT

Para a maioria dos códigos C#, é simples determinar qual código nativo precisa ser gerado. O compilador nativo pode percorrer os corpos do método e encontrar quais estruturas de dados e código nativo são acessados. Infelizmente, alguns recursos, como reflexão, apresentam um problema significativo. Considere o seguinte código:

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

struct GenericType<T> { }

Embora o programa acima não seja muito útil, ele representa um caso extremo que requer um número infinito de tipos genéricos a serem criados ao compilar o aplicativo como AOT Nativo. Sem AOT Nativo, o programa seria executado até ficar sem memória. Com o AOT Nativo, não conseguiríamos nem mesmo compilá-lo se gerássemos todos os tipos necessários (o número infinito deles).

Nesse caso, o build de AOT Nativo emite o seguinte aviso na linha 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.

Em tempo de execução, o aplicativo realmente lançará uma exceção da chamada MakeGenericType.

Reação aos avisos de AOT

Os avisos de AOT destinam-se a trazer previsibilidade para builds de AOT Nativos. A maioria dos avisos de AOT é sobre uma possível exceção em tempo de execução em situações em que o código nativo não foi gerado para dar suporte ao cenário. A categoria mais ampla é RequiresDynamicCodeAttribute.

RequiresDynamicCode

RequiresDynamicCodeAttribute é simples e amplo: é um atributo que significa que o membro foi anotado como sendo incompatível com a AOT. Essa anotação significa que o membro pode usar reflexão ou outro mecanismo para criar um novo código nativo em tempo de execução. Esse atributo é usado quando o código não é fundamentalmente compatível com AOT ou a dependência nativa é muito complexa para prever estaticamente no tempo de build. Isso costuma ser verdadeiro para métodos que usam a API Type.MakeGenericType, a emissão de reflexão ou outras tecnologias de geração de código em tempo de execução. O código a seguir mostra um exemplo.

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

Não há muitas soluções alternativas para RequiresDynamicCode. A melhor correção é evitar chamar o método ao compilar como AOT Nativo e usar outra coisa compatível com AOT. Se você estiver escrevendo uma biblioteca e não estiver no seu controle se deseja ou não o método, adicione RequiresDynamicCode ao seu método. Isso anotará o método como não compatível com AOT. A adição de RequiresDynamicCode silencia todos os avisos de AOT no método anotado, mas produzirá um aviso sempre que outra pessoa o chamar. Por esse motivo, é mais útil para os autores de biblioteca deixar o aviso ser gerado para uma API pública.

Se de alguma forma você puder determinar que a chamada é segura e todo o código nativo estará disponível em tempo de execução, você também poderá suprimir o aviso usando UnconditionalSuppressMessageAttribute. Por exemplo:

[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 é como SuppressMessage , mas pode ser visto por publish e outras ferramentas pós-build. As diretivas SuppressMessage e #pragma só estão presentes na origem para que não possam ser usadas para silenciar avisos de build.

Cuidado

Tenha cuidado ao suprimir avisos de AOT. A chamada pode ser compatível com AOT agora, mas à medida que você atualiza seu código, isso pode mudar e você pode esquecer de revisar todas as supressões.