Geração de código no Orleans

Antes do Orleans 7.0, a geração de origem era muito mais manual e exigia uma intervenção explícita do desenvolvedor. A partir do Orleans 7.0, a geração de código é automática e não requer nenhuma intervenção do desenvolvedor. No entanto, ainda há casos em que os desenvolvedores podem querer influenciar a geração de código, por exemplo, para gerar código para tipos que não são gerados automaticamente ou para gerar código para tipos em outro assembly.

Habilitar geração de código

Orleans gera o código-fonte C# para seu aplicativo no momento da compilação. Todos os projetos, incluindo o host, precisam ter os pacotes NuGet apropriados instalados para habilitar a geração de código. Os seguintes pacotes estão disponíveis:

Use o GenerateSerializerAttribute para especificar que o tipo se destina a ser serializado e que o código de serialização deve ser gerado para o tipo. Para obter mais informações, confira Usar serialização doOrleans.

O runtime do Orleans usa código gerado para garantir a serialização adequada dos tipos usados no cluster, bem como para gerar o clichê, que abstrai os detalhes de implementação de envio de métodos, propagação de exceção e outros conceitos internos do runtime. A geração de código pode ser executada quando seus projetos estão sendo construídos ou quando seu aplicativo é inicializado.

O que acontece durante a construção?

No momento da compilação, o Orleans gera código para todos os tipos marcados com GenerateSerializerAttribute. Se um tipo não estiver marcado comGenerateSerializer, ele não será serializado pelo Orleans.

Se você estiver desenvolvendo com F# ou Visual Basic, também poderá usar a geração de código. Para saber mais, veja a seção os exemplos a seguir:

Esses exemplos demonstram como consumir o Orleans.GenerateCodeForDeclaringAssemblyAttribute, especificando os tipos no assembly para os quais o gerador de origem deve inspecionar e gerar a origem.

O método preferencial para realizar a geração de código é em tempo de compilação. A geração de código de tempo de compilação pode ser habilitada usando um dos seguintes pacotes:

  • Microsoft.Orleans.OrleansCodeGenerator.Build. Um pacote que usa Roslyn para geração de código e usa .NET Reflection para análise.
  • Microsoft.Orleans.CodeGenerator.MSBuild. Um novo pacote de geração de código que aproveita o Roslyn tanto para geração de código quanto para análise de código. Ele não carrega binários de aplicativos e, como resultado, evita problemas causados por versões de dependência conflitantes e estruturas de destino diferentes. O novo gerador de código também melhora o suporte para compilações incrementais, o que deve resultar em tempos de compilação mais curtos.

Um desses pacotes deve ser instalado em todos os projetos que contêm grãos, interfaces de grãos, serializadores personalizados ou tipos que são enviados entre grãos. A instalação de um pacote injeta um destino no projeto que gerará código em tempo de compilação.

Ambos os pacotes (Microsoft.Orleans.CodeGenerator.MSBuild e Microsoft.Orleans.OrleansCodeGenerator.Build) dão suporte apenas a projetos C#. Outras linguagens são suportadas usando o Microsoft.Orleans.OrleansCodeGenerator pacote descrito abaixo ou criando um projeto C# que pode atuar como destino para código gerado a partir de assemblies escritos em outras linguagens.

Diagnósticos adicionais podem ser emitidos em tempo de compilação especificando um valor para OrleansCodeGenLogLevel no arquivo .csproj do projeto de destino. Por exemplo, <OrleansCodeGenLogLevel>Trace</OrleansCodeGenLogLevel>.

O que acontece durante a inicialização?

Em Orleans 7+, nada acontece durante a inicialização. A geração de código é executada no momento da compilação.

A geração de código pode ser realizada durante a inicialização no cliente e no silo instalando o Microsoft.Orleans.OrleansCodeGenerator pacote e usando o ApplicationPartManagerCodeGenExtensions.WithCodeGeneration método de extensão:

builder.ConfigureApplicationParts(
    parts => parts
        .AddApplicationPart(typeof(IRuntimeCodeGenGrain).Assembly)
        .WithCodeGeneration());

No exemplo anterior, builder pode ser uma instância de ISiloHostBuilder ou IClientBuilder. Uma ILoggerFactory instância opcional pode ser passada WithCodeGeneration para habilitar o log durante a geração de código, por exemplo:

ILoggerFactory codeGenLoggerFactory = new LoggerFactory();
codeGenLoggerFactory.AddProvider(new ConsoleLoggerProvider());
    builder.ConfigureApplicationParts(
        parts => parts
            .AddApplicationPart(typeof(IRuntimeCodeGenGrain).Assembly)
            .WithCodeGeneration(codeGenLoggerFactory));

Influenciar a geração de código

Ao aplicar GenerateSerializerAttribute a um tipo, você também pode aplicar o IdAttribute para identificar exclusivamente o membro. Da mesma forma, você também pode aplicar um alias com o AliasAttribute. Para obter mais informações sobre como influenciar a geração de código, consulte Usar Orleans serialização.

Durante a geração de código, você pode influenciar a geração de código para um tipo específico. O código é gerado automaticamente para interfaces granulares, classes granulares, estado granular e tipos passados como argumentos em métodos granulares. Se um tipo não se ajustar a esses critérios, os métodos a seguir poderão ser usados para orientar ainda mais a geração de código.

Adicionar SerializableAttribute a um tipo instrui o gerador de código a gerar um serializador para esse tipo.

Adicionar [assembly: GenerateSerializer(Type)] a um projeto instrui o gerador de código a tratar esse tipo como serializável e causará um erro se um serializador não puder ser gerado para esse tipo, por exemplo, porque o tipo não é acessível. Este erro interromperá uma compilação se a geração de código estiver habilitada. Este atributo também permite gerar código para tipos específicos de outro assembly.

[assembly: KnownType(Type)] também instrui o gerador de código a incluir um tipo específico (que pode ser de um assembly referenciado), mas não causa uma exceção se o tipo for inacessível.

Gerar serializadores para todos os subtipos

Adicionar KnownBaseTypeAttribute a uma interface ou classe instrui o gerador de código a gerar código de serialização para todos os tipos que herdam/implementam esse tipo.

Gerar código para todos os tipos em outro assembly

Há casos em que o código gerado não pode ser incluído em um assembly específico no momento da compilação. Por exemplo, isso pode incluir bibliotecas compartilhadas que não fazem referência a Orleans, assemblies escritos em linguagens diferentes do C# e assemblies em que o desenvolvedor não tem o código-fonte. Nesses casos, o código gerado para esses assemblies pode ser colocado em um assembly separado que é referenciado durante a inicialização.

Para habilitar isso para um assembly:

  1. Criar um Projeto C#
  2. Instalar o pacote Microsoft.Orleans.CodeGenerator.MSBuild ou Microsoft.Orleans.OrleansCodeGenerator.Build.
  3. Adicione uma referência ao assembly de destino.
  4. Adicione [assembly: KnownAssembly("OtherAssembly")] no nível superior de um arquivo C#.

O KnownAssemblyAttribute instrui o gerador de código a inspecionar o assembly especificado e gerar código para os tipos dentro dele. O atributo pode ser usado várias vezes em um projeto.

O assembly gerado deve ser adicionado ao cliente/silo durante a inicialização:

builder.ConfigureApplicationParts(
    parts => parts.AddApplicationPart("CodeGenAssembly"));

No exemplo anterior, builder pode ser uma instância de ISiloHostBuilder ou IClientBuilder.

KnownAssemblyAttribute tem uma propriedade opcional, TreatTypesAsSerializable, que pode ser definida como true para instruir o gerador de código a agir como se todos os tipos dentro desse assembly estivessem marcados como serializáveis.