Partage via


Générateurs de source

Cet article fournit une vue d’ensemble des Générateurs de sources inclus dans le SDK .NET Compiler Platform (« Roslyn »). Les générateurs de source permettent aux développeurs C# d’inspecter le code utilisateur à mesure qu’il est compilé. Le générateur peut créer de nouveaux fichiers sources C# à la volée qui sont ajoutés à la compilation de l’utilisateur. De cette façon, vous disposez d’un code qui s’exécute pendant la compilation. Il inspecte votre programme pour produire des fichiers sources supplémentaires compilés avec le reste de votre code.

Un générateur de source est un nouveau type de composant que les développeurs C# peuvent écrire et qui vous permet d’effectuer deux choses importantes :

  1. Récupérer un objet de compilation qui représente tout le code utilisateur en cours de compilation. Cet objet peut être inspecté et vous pouvez écrire du code qui fonctionne avec la syntaxe et les modèles sémantiques pour le code en cours de compilation, comme avec les analyseurs d’aujourd’hui.

  2. Générez des fichiers sources C# qui peuvent être ajoutés à un objet de compilation pendant la compilation. Autrement dit, vous pouvez fournir du code source supplémentaire comme entrée dans une compilation pendant la compilation.

Lorsqu’ils sont combinés, ces deux éléments rendent les générateurs de source si utiles. Vous pouvez inspecter le code utilisateur avec toutes les métadonnées enrichies générées par le compilateur pendant la compilation. Votre générateur émet ensuite du code C# dans la même compilation, qui est basée sur les données que vous avez analysées. Si vous connaissez les analyseurs Roslyn, vous pouvez considérer les générateurs de source comme des analyseurs capables d’émettre du code source C#.

Les générateurs de source s’exécutent en tant que phase de compilation visualisées ci-dessous :

Graphique décrivant les différentes parties de la génération source

Un générateur de source est un assembly .NET Standard 2.0 chargé par le compilateur avec tous les analyseurs. Il est utilisable dans les environnements où les composants .NET Standard peuvent être chargés et exécutés.

Important

Actuellement, seuls les assemblys .NET Standard 2.0 peuvent être utilisés comme générateurs de source.

Scénarios courants

Il existe trois approches générales pour inspecter le code utilisateur et générer des informations ou du code basé sur l’analyse utilisée par les technologies d’aujourd’hui :

  • Réflexion du runtime.
  • Jongler les tâches MSBuild.
  • Tissage en langage intermédiaire (IL) (non abordé dans cet article).

Les générateurs de source peuvent être une amélioration par rapport à chaque approche.

Réflexion du runtime

La réflexion d’exécution est une technologie puissante qui a été ajoutée à .NET il y a longtemps. Il existe d’innombrables scénarios pour l’utiliser. Un scénario courant consiste à effectuer une analyse du code utilisateur lors du démarrage d’une application et à utiliser ces données pour générer des éléments.

Par exemple, ASP.NET Core utilise la réflexion lorsque votre service web s’exécute pour la première fois, afin de découvrir les constructions que vous avez définies et de sorte qu’il puisse « brancher » des éléments tels que des contrôleurs et des pages Razor. Bien que cela vous permette d’écrire du code simple avec des abstractions puissantes, il s’accompagne d’une pénalité de performances au moment de l’exécution : lorsque votre service web ou votre application démarre pour la première fois, il/elle ne peut accepter aucune demande tant que tout le code de réflexion du runtime qui découvre des informations sur votre code n’est pas terminé. Bien que cette pénalité de performance ne soit pas énorme, il s’agit d’un coût fixe que vous ne pouvez pas améliorer vous-même dans votre propre application.

Avec un générateur de source, la phase de découverte du contrôleur au démarrage peut se produire au moment de la compilation. Un générateur peut analyser votre code source et émettre le code dont il a besoin pour « brancher » votre application. L’utilisation de générateurs de source peut accélérer les temps de démarrage, car une action se produisant au moment de l’exécution aujourd’hui peut être poussée au moment de la compilation.

Jongler les tâches MSBuild

Les générateurs de source peuvent améliorer les performances autrement que par la réflexion au moment de l’exécution, afin de découvrir aussi des types. Certains scénarios impliquent d’appeler la tâche C# MSBuild (appelée CSC) plusieurs fois, afin qu’elles puissent inspecter les données d’une compilation. Comme vous pouvez l’imaginer, appeler plusieurs fois le compilateur, affecte le temps total nécessaire à la création de votre application. Nous examinons comment les générateurs de sources peuvent être utilisés pour ne pas avoir à jongler les tâches MSBuild comme celle-ci, car les générateurs de source n’offrent pas seulement certains avantages en matière de performances, mais permettent également aux outils de fonctionner au bon niveau d’abstraction.

Une autre fonctionnalité que les générateurs de sources peuvent offrir est l’évitement de l’utilisation de certaines API « typées par chaîne », telles que la façon dont le routage ASP.NET Core fonctionne entre les contrôleurs et les pages Razor. Avec un générateur de source, le routage peut être fortement typé avec les chaînes nécessaires générées comme détail au moment de la compilation. Ceci réduirait le nombre de fois où un littéral de chaîne mal typé conduit à une requête qui n’atteint pas le contrôleur correct.

Démarrage avec les générateurs de source

Dans ce guide, vous allez explorer la création d’un générateur de source à l’aide de l’API ISourceGenerator.

  1. Créer une application console .NET. Cet exemple utilise .NET 6.

  2. Remplacez la classe Program par le code suivant : Le code suivant n’utilise pas d’instructions de niveau supérieur. Le formulaire classique est requis, car ce premier générateur de source écrit une méthode partielle dans cette classe Program :

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

    Notes

    Vous pouvez exécuter cet exemple tel qu’il est, mais rien ne se passera pour l’instant.

  3. Ensuite, nous allons créer un projet de générateur de source qui implémentera l’équivalent de la méthode partial void HelloFrom.

  4. Créez un projet de bibliothèque standard .NET qui cible le moniker de framework cible (TFM) netstandard2.0. Ajoutez les packages NuGet Microsoft.CodeAnalysis.Analyzers et Microsoft.CodeAnalysis.CSharp :

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
      </ItemGroup>
    
    </Project>
    

    Conseil

    Le projet de générateur de source doit cibler le TFM netstandard2.0, sinon il ne fonctionnera pas.

  5. Créez un nouveau fichier C# nommé HelloSourceGenerator.cs qui spécifie votre propre générateur de source comme suit :

    using Microsoft.CodeAnalysis;
    
    namespace SourceGenerator
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                // Code generation goes here
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
                // No initialization required for this one
            }
        }
    }
    

    Un générateur de source doit à la fois implémenter l’interface Microsoft.CodeAnalysis.ISourceGenerator et disposer de Microsoft.CodeAnalysis.GeneratorAttribute. Tous les générateurs de source ne nécessitent pas d’initialisation, et c’est le cas avec cet exemple d’implémentation, où ISourceGenerator.Initialize est vide.

  6. Remplacez le contenu de la méthode ISourceGenerator.Execute par l’implémentation suivante :

    using Microsoft.CodeAnalysis;
    
    namespace SourceGenerator
    {
        [Generator]
        public class HelloSourceGenerator : ISourceGenerator
        {
            public void Execute(GeneratorExecutionContext context)
            {
                // Find the main method
                var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);
    
                // Build up the source code
                string source = $@"// <auto-generated/>
    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}}'"");
        }}
    }}
    ";
                var typeName = mainMethod.ContainingType.Name;
    
                // Add the source code to the compilation
                context.AddSource($"{typeName}.g.cs", source);
            }
    
            public void Initialize(GeneratorInitializationContext context)
            {
                // No initialization required for this one
            }
        }
    }
    

    À partir de l’objet context , nous pouvons accéder au point d’entrée des compilations ou à la méthode Main. L’instance mainMethod est un IMethodSymbol, et elle représente une méthode ou un symbole de type méthode (y compris constructeur, destructeur, opérateur ou accesseur de propriété/événement). La méthode Microsoft.CodeAnalysis.Compilation.GetEntryPoint retourne le IMethodSymbol pour le point d’entrée du programme. D’autres méthodes vous permettent de trouver n’importe quel symbole de méthode dans un projet. À partir de cet objet, nous pouvons raisonner à propos de l’espace de noms contenant (le cas échéant) et le type. Le source de cet exemple est une chaîne interpolée qui modélise le code source à générer, où les espaces interpolés sont remplis avec les informations d’espace de noms et de type contenues. Le source est ajouté à context avec un nom indicateur. Pour cet exemple, le générateur crée un fichier source généré qui contient une implémentation de la méthode partial dans l’application console. Vous pouvez écrire des générateurs de source pour ajouter n’importe quelle source de votre choix.

    Conseil

    Le paramètre hintName de la méthode GeneratorExecutionContext.AddSource peut être n’importe quel nom unique. Il est courant de fournir une extension de fichier C# explicite telle que ".g.cs" ou ".generated.cs" pour le nom. Le nom de fichier permet d’identifier le fichier en tant que source générée.

  7. Nous disposons maintenant d’un générateur fonctionnel, mais nous devons le connecter à notre application console. Modifiez le projet d’application console d’origine et ajoutez les éléments suivants, en remplaçant le chemin du projet par celui du projet .NET Standard que vous avez créé ci-dessus :

    <!-- Add this as a new ItemGroup, replacing paths and names appropriately -->
    <ItemGroup>
        <ProjectReference Include="..\PathTo\SourceGenerator.csproj"
                          OutputItemType="Analyzer"
                          ReferenceOutputAssembly="false" />
    </ItemGroup>
    

    Cette nouvelle référence n’est pas une référence de projet traditionnelle et doit être modifiée manuellement pour inclure les attributs OutputItemType et ReferenceOutputAssembly. Pour plus d’informations sur les attributs OutputItemType et ReferenceOutputAssembly de ProjectReference, consultez Éléments de projet MSBuild courants : ProjectReference.

  8. À présent, lorsque vous exécutez l’application console, vous devez voir que le code généré est exécuté et s’affiche à l’écran. L’application console elle-même n’implémente pas la méthode HelloFrom, mais plutôt sa source générée lors de la compilation à partir du projet Générateur de source. Le texte suivant est un exemple de sortie de l’application :

    Generator says: Hi from 'Generated Code'
    

    Notes

    Vous devrez peut-être redémarrer Visual Studio pour voir IntelliSense et vous débarrasser des erreurs, car l’expérience d’outils est améliorée activement.

  9. Si vous utilisez Visual Studio, vous pouvez voir les fichiers sources générés. Dans la fenêtre Explorateur de solutions, développez les Dépendances>Analyzers>SourceGenerator>SourceGenerator.HelloSourceGeneratoret double-cliquez sur le fichier Program.g.cs.

    Visual Studio : Fichiers sources générés par l’Explorateur de solutions.

    Lorsque vous ouvrez ce fichier généré, Visual Studio indique que le fichier est généré automatiquement et qu’il ne peut pas être modifié.

    Visual Studio : fichier Program.g.cs généré automatiquement.

  10. Vous pouvez également définir des propriétés de build pour enregistrer le fichier généré et contrôler l’emplacement où les fichiers générés sont stockés. Dans le fichier projet de l’application console, ajoutez l’élément <EmitCompilerGeneratedFiles> à un <PropertyGroup> et définissez sa valeur sur true. Regénérez votre projet. À présent, les fichiers générés sont créés sous obj/Debug/net6.0/generated/SourceGenerator/SourceGenerator.HelloSourceGenerator. Les composants du chemin d’accès sont mappés à la configuration de build, à la version cible de .Net Framework, au nom du projet du générateur de source et au nom de type complet du générateur. Vous pouvez choisir un dossier de sortie plus pratique en ajoutant l’élément <CompilerGeneratedFilesOutputPath> au fichier projet de l’application.

Étapes suivantes

Le cookbook des générateurs de source passe en revue certains de ces exemples avec quelques approches recommandées pour les résoudre. En outre, nous avons un ensemble d’échantillons disponibles sur GitHub que vous pouvez essayer vous-même.

Pour en savoir plus sur les générateurs de source, consultez les articles suivants :