Generadores de código fuente

En este artículo se ofrece información general sobre los generadores de código fuente que se incluye como parte del SDK de .NET Compiler Platform ("Roslyn"). Los generadores de código fuente son una característica del compilador de C# que permite a los desarrolladores de C# inspeccionar el código de usuario mientras se compilan y generan nuevos archivos de código fuente de C# sobre la marcha que se agregan a la compilación del usuario.

Un generador de código fuente es un fragmento de código que se ejecuta durante la compilación y puede inspeccionar el programa para generar archivos de código fuente adicionales que se compilan junto con el resto del código.

Un generador de código fuente es un nuevo tipo de componente que los desarrolladores de C# pueden escribir y que le permite hacer dos cosas principales:

  1. Recuperar un objeto de compilación que representa todo el código de usuario que se está compilando. Este objeto se puede inspeccionar, y puede escribir código que funcione con la sintaxis y los modelos semánticos del código que se compila, al igual que con los analizadores de hoy en día.

  2. Genere archivos de código fuente de C# que se puedan agregar a un objeto de compilación durante el transcurso de la compilación. En otras palabras, puede proporcionar código fuente adicional como una entrada en una compilación mientras se compila el código.

Cuando se combinan, estas dos cosas son las que hacen que los generadores de código fuente sean tan útiles. Puede inspeccionar el código de usuario con todos los metadatos enriquecidos que el compilador crea durante la compilación y, a continuación, emitir código de C# en la misma compilación que se basa en los datos que ha analizado. Si está familiarizado con los analizadores de Roslyn, puede pensar en los generadores de código fuente como analizadores que pueden emitir código fuente de C#.

Los generadores de código fuente se ejecutan como una fase de compilación que se visualiza a continuación:

Gráfico que describe las distintas partes de la generación de código fuente

Un generador de código fuente es un ensamblado de .NET Standard 2.0 que el compilador carga junto con cualquier analizador. Se puede usar en entornos en los que se pueden cargar y ejecutar los componentes de .NET Standard.

Escenarios frecuentes

En la actualidad, hay tres enfoques generales para inspeccionar el código de usuario y generar información o código basado en ese análisis que usan las tecnologías de hoy en día: reflexión en tiempo de ejecución, entrelazado de IL y obviar las tareas de MSBuild. Los generadores de código fuente pueden mejorar cada enfoque. La reflexión en tiempo de ejecución es una tecnología eficaz que se agregó a .NET hace mucho tiempo. Existen incontables escenarios para usarla. Un escenario muy común es realizar algún análisis del código de usuario cuando se inicia una aplicación y usar los datos para generar elementos.

Por ejemplo, ASP.NET Core usa la reflexión cuando el servicio web se ejecuta por primera vez para detectar las construcciones que ha definido para que pueda "conectar" elementos, como controladores y Razor Pages. Aunque esto le permite escribir código sencillo con abstracciones eficaces, incluye una penalización de rendimiento en tiempo de ejecución: cuando el servicio web o la aplicación se inician por primera vez, no puede aceptar ninguna solicitud hasta que todo el código de reflexión en tiempo de ejecución que descubre información sobre el código termine de ejecutarse. Aunque esta penalización de rendimiento no es enorme, es un costo fijo que no puede mejorar en su propia aplicación.

Con un generador de código fuente, la fase de detección del controlador de inicio podría producirse en tiempo de compilación mediante el análisis del código fuente y la emisión del código que necesita para "conectar" la aplicación. Esto podría dar lugar a unos tiempos de inicio más rápidos, ya que una acción que se está produciendo en tiempo de ejecución podría insertarse en tiempo de compilación. Los generadores de origen pueden mejorar el rendimiento de formas que no se limitan a la reflexión en tiempo de ejecución para descubrir tipos también. Algunos escenarios implican llamar a la tarea de C# de MSBuild (denominada CSC) varias veces para que puedan inspeccionar los datos desde una compilación. Como puede imaginar, llamar al compilador más de una vez afecta al tiempo total que se tarda en compilar la aplicación. Estamos investigando cómo se pueden usar los generadores de código fuente para obviar la necesidad de realizar tareas de MSBuild como esta, ya que los generadores de código fuente no solo ofrecen algunas ventajas de rendimiento, sino que también permiten que las herramientas funcionen en el nivel de abstracción correcto.

Otra funcionalidad que los generadores de código fuente pueden ofrecer es obviar el uso de algunas API "con tipo de cadena"; por ejemplo, cómo funciona el enrutamiento de ASP.NET Core entre controladores y Razor Pages. Con un generador de código fuente, el enrutamiento puede estar fuertemente tipado con la generación de las cadenas necesarias como un detalle en tiempo de compilación. Esto reduciría la cantidad de veces que un literal de cadena mal escrito genera una solicitud que no llega al controlador correcto.

Introducción a los generadores de código fuente

En esta guía, explorará la creación de un generador de código fuente mediante la API ISourceGenerator.

  1. Creación de una aplicación de consola .NET

  2. Reemplace la clase Program por lo siguiente:

    partial class Program
    {
        static void Main(string[] args)
        {
            HelloFrom("Generated Code");
        }
    
        static partial void HelloFrom(string name);
    }
    

    Nota

    Puede ejecutar este ejemplo tal y como está, pero todavía no ocurrirá nada.

  3. A continuación, crearemos un generador de código fuente que rellenará el contenido del método HelloFrom.

  4. Cree un proyecto de biblioteca de .NET Standard con este aspecto:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" />
      </ItemGroup>
    
    </Project>
    
  5. Modifique o cree un archivo de C# que especifique su propio generador de código fuente de la siguiente manera:

    using Microsoft.CodeAnalysis;
    
    namespace MyGenerator
    {
        [Generator]
        public class MySourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                // Code generation goes here
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
                // No initialization required for this one
            }
        }
    }
    
  6. Reemplace el contenido del método Execute para que sea similar al siguiente:

    public void Execute(GeneratorExecutionContext context)
    {
        // find the main method
        var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
    
        // build up the source code
        string source = $@"
    using System;
    
    namespace {mainMethod.ContainingNamespace.ToDisplayString()}
    {{
        public static partial class {mainMethod.ContainingType.Name}
        {{
            static partial void HelloFrom(string name)
            {{
                Console.WriteLine($""Generator says: Hi from '{{name}}'"");
            }}
        }}
    }}
    ";
        // add the source code to the compilation
        context.AddSource("generatedSource", source);
    }
    
  7. Ahora tenemos un generador en funcionamiento, pero es necesario conectarlo a nuestra aplicación de consola. Edite el proyecto de aplicación de consola original y agregue lo siguiente, reemplazando la ruta de acceso del proyecto por la del proyecto de .NET Standard que creó anteriormente:

      <!-- Add this as a new ItemGroup, replacing paths and names appropriately -->
      <ItemGroup>
        <!-- Note that this is not a "normal" ProjectReference.
             It needs the additional 'OutputItemType' and 'ReferenceOutputAssembly' attributes. -->
        <ProjectReference Include="path-to-sourcegenerator-project.csproj"
                          OutputItemType="Analyzer"
                          ReferenceOutputAssembly="false" />
      </ItemGroup>
    
  8. Ahora, al ejecutar la aplicación de consola, debería ver que el código generado se ejecuta e imprime en la pantalla.

    Nota

    Actualmente, tendrá que reiniciar Visual Studio para ver IntelliSense y eliminar los errores con la experiencia de herramientas inicial.

Pasos siguientes

En la guía paso a paso de los generadores de código fuente se abordan algunos de estos ejemplos con algunos enfoques recomendados para resolverlos. Además, tenemos un conjunto de ejemplos disponibles en GitHub que puede probar por su cuenta.

Puede obtener más información sobre los generadores de código fuente en estos temas: