Generación de código de Orleans

Antes de la versión 7.0 Orleans, la generación de origen era mucho más manual y requería una intervención explícita del desarrollador. A partir de la versión 7.0 Orleans, la generación de código es automática y no requiere la intervención del desarrollador. Sin embargo, todavía hay casos en los que los desarrolladores pueden querer influir en la generación de código, por ejemplo, para generar código para tipos que no se generan automáticamente o generar código para tipos en otro ensamblado.

Generación de código fuente

Orleans genera código fuente de C# para la aplicación en tiempo de compilación. Todos los proyectos, incluido el host, deben tener instalados los paquetes NuGet adecuados para habilitar la generación de código. Están disponibles los siguientes paquetes:

Use GenerateSerializerAttribute para especificar que el tipo está pensado para serializarse y que se debe generar el código de serialización para el tipo. Para obtener más información, consulte Uso de serialización Orleans.

El entorno de ejecución de Orleans usa código generado para garantizar la serialización adecuada de los tipos que se usan en el clúster, así como para generar código reutilizable, que abstrae los detalles de implementación del envío de métodos, la propagación de excepciones y otros conceptos internos del entorno de ejecución. La generación de código se puede realizar cuando se compilan los proyectos o cuando se inicializa la aplicación.

¿Qué ocurre durante la compilación?

En tiempo de compilación, Orleans genera código para todos los tipos marcados con GenerateSerializerAttribute. Si un tipo no está marcado con GenerateSerializer, no se serializará mediante Orleans.

Si va a desarrollar con F# o Visual Basic, también puede usar la generación de código. Para obtener más información, consulte los ejemplos siguientes:

En estos ejemplos se muestra cómo consumir Orleans.GenerateCodeForDeclaringAssemblyAttribute, especificando los tipos del ensamblado para los que el generador de origen debe inspeccionar y generar el origen.

El método preferido para realizar la generación de código es en tiempo de compilación. La generación de código en tiempo de compilación podría habilitarse mediante uno de los siguientes paquetes:

  • Microsoft.Orleans.OrleansCodeGenerator.Build. Paquete que usa Roslyn para la generación de código y usa reflexión de .NET para el análisis.
  • Microsoft.Orleans.CodeGenerator.MSBuild. Un nuevo paquete de generación de código que aprovecha Roslyn tanto para la generación de código como para el análisis de código. No carga archivos binarios de aplicación y, como resultado, evita problemas causados por conflictos con las versiones de dependencia y las distintas plataformas de destino. El nuevo generador de código también mejora la compatibilidad con las compilaciones incrementales, lo que debería dar lugar a tiempos de compilación más cortos.

Uno de estos paquetes debe instalarse en todos los proyectos que contienen granos, interfaces de grano, serializadores personalizados o tipos que se envían entre granos. La instalación de un paquete inserta un destino en el proyecto que generará código en tiempo de compilación.

Ambos paquetes (Microsoft.Orleans.CodeGenerator.MSBuild y Microsoft.Orleans.OrleansCodeGenerator.Build) solo admiten proyectos de C#. Se admiten otros lenguajes mediante el Microsoft.Orleans.OrleansCodeGenerator paquete que se describe a continuación o mediante la creación de un proyecto de C# que puede actuar como destino para el código generado a partir de ensamblados escritos en otros lenguajes.

Se pueden emitir diagnósticos adicionales en tiempo de compilación especificando el valor de OrleansCodeGenLogLevel en el archivo csproj del proyecto de destino. Por ejemplo: <OrleansCodeGenLogLevel>Trace</OrleansCodeGenLogLevel>.

¿Qué ocurre durante la inicialización?

En 7+ Orleans, no ocurre nada durante la inicialización. La generación de código se realiza en tiempo de compilación.

La generación de código se puede realizar durante la inicialización en el cliente y el silo mediante la instalación del paquete Microsoft.Orleans.OrleansCodeGenerator y el uso del método de extensión ApplicationPartManagerCodeGenExtensions.WithCodeGeneration:

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

En el ejemplo anterior, builder puede ser una instancia de ISiloHostBuilder o IClientBuilder. Se puede pasar una instancia opcional ILoggerFactory a WithCodeGeneration para habilitar el registro durante la generación de código, por ejemplo:

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

Influir en la generación de código

Al aplicar GenerateSerializerAttribute a un tipo, también puede aplicar IdAttribute para identificar de forma única el miembro. Del mismo modo, también puede aplicar un alias con AliasAttribute. Para obtener más información sobre cómo influir en la generación de código, consulte Usode la serialización Orleans.

Durante la generación de código, puede influir en la generación de código para un tipo específico. El código se genera automáticamente para interfaces de grano, clases de grano, estado de grano y tipos pasados como argumentos en métodos de grano. Si un tipo no se ajusta a estos criterios, se pueden usar los métodos siguientes para guiar aún más la generación de código.

Al agregar SerializableAttribute a un tipo se indica al generador de código que genere un serializador para ese tipo.

Al agregar [assembly: GenerateSerializer(Type)] a un proyecto se indica al generador de código que trate ese tipo como serializable y provocará un error si no se pudo generar un serializador para ese tipo, por ejemplo, porque el tipo no es accesible. Este error detendrá una compilación si la generación de código está habilitada. Este atributo también permite generar código para tipos específicos de otro ensamblado.

[assembly: KnownType(Type)] también indica al generador de código que incluya un tipo específico (que puede ser de un ensamblado al que se hace referencia), pero no provoca una excepción si el tipo no es accesible.

Generación de serializadores para todos los subtipos

Al agregar KnownBaseTypeAttribute a una interfaz o clase se indica al generador de código que genere código de serialización para todos los tipos que heredan o implementan ese tipo.

Generación de código para todos los tipos de otro ensamblado

Hay casos en los que el código generado no se puede incluir en un ensamblado determinado en tiempo de compilación. Por ejemplo, puede incluir bibliotecas compartidas que no hacen referencia a Orleans, ensamblados escritos en lenguajes distintos de C#, y ensamblados en los que el desarrollador no tiene el código fuente. En estos casos, el código generado para esos ensamblados se puede colocar en un ensamblado independiente al que se hace referencia durante la inicialización.

Para habilitar esto para un ensamblado:

  1. Crear un proyecto de C#.
  2. Instalar el paquete Microsoft.Orleans.CodeGenerator.MSBuild o Microsoft.Orleans.OrleansCodeGenerator.Build.
  3. Agregue una referencia al ensamblado de destino.
  4. Agregue [assembly: KnownAssembly("OtherAssembly")] en el nivel superior de un archivo de C#.

KnownAssemblyAttribute indica al generador de código que inspeccione el ensamblado especificado y genere código para los tipos que contiene. El atributo se puede usar varias veces dentro de un proyecto.

Después, el ensamblado generado debe agregarse al cliente o silo durante la inicialización:

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

En el ejemplo anterior, builder puede ser una instancia de ISiloHostBuilder o IClientBuilder.

KnownAssemblyAttribute tiene una propiedad opcional, TreatTypesAsSerializable, que se puede establecer en true para indicar al generador de código que actúe como si todos los tipos de ese ensamblado se marcan como serializables.