Chargement...

Le concept des générateurs de code en C# (C# 9+)

Découvrez les générateurs de code en C# pour générer du code à la compilation et améliorer les performances.

Introduit avec C# 9, les Source Generators sont une puissante fonctionnalité de Roslyn permettant de générer du code C# supplémentaire au moment de la compilation. Ce mécanisme crée de nouveaux fichiers de code pendant la compilation et non à l’exécution. Ainsi, le code répétitif est généré automatiquement sans coût d’exécution, améliorant les performances et réduisant la maintenance.


Qu’est-ce qu’un Source Generator ?

Un Source Generator est un composant ajouté au compilateur. Le compilateur (Roslyn) analyse le code source de votre projet et ajoute automatiquement de nouvelles classes ou méthodes là où c’est nécessaire. Le code généré est inclus au moment de la compilation et peut être utilisé par le développeur comme du code C# classique.


// Résumé : un Source Generator est un plugin du compilateur.
// Le développeur écrit une logique personnalisée, le générateur produit du code.
// À la fin de la compilation, le code généré est automatiquement ajouté au projet.

Quand l’utiliser ?


Exemple simple de Source Generator

Voici un exemple minimaliste d’un Source Generator. Ce générateur crée une nouvelle classe HelloGenerated pendant la compilation.


// HelloGenerator.csproj (doit être une bibliothèque de classes séparée)
// Package : Microsoft.CodeAnalysis.CSharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class HelloGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // S’exécute au début de l’analyse si nécessaire (ex. écoute de la syntaxe)
    }

    public void Execute(GeneratorExecutionContext context)
    {
        string code = @"
namespace HelloGen
{
    public static class HelloGenerated
    {
        public static void SayHello() =>
            System.Console.WriteLine(""Bonjour ! Cette classe a été générée au moment de la compilation."");
    }
}";
        context.AddSource("HelloGenerated.g.cs", SourceText.From(code, Encoding.UTF8));
    }
}

// Utilisation dans le projet principal
using HelloGen;

class Program
{
    static void Main()
    {
        HelloGenerated.SayHello(); // Appelle le code généré à la compilation
    }
}

Ce fichier de code est généré automatiquement pendant la compilation. Dans Visual Studio, il peut être visualisé sous « Analyzers → HelloGenerator → HelloGenerated.g.cs ».


Configuration : création d’un projet de générateur

  1. Créez un nouveau projet Class Library (.NET Standard).
  2. Ajoutez les propriétés suivantes au fichier projet :

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <AnalyzerLanguage>C#</AnalyzerLanguage>
    <OutputItemType>Analyzer</OutputItemType>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
  </ItemGroup>
</Project>

Grâce à cette configuration, le projet devient un « Analyzer/Generator ». Vous pouvez ensuite référencer cette DLL comme un « Analyzer » dans le projet principal.


Génération de code basée sur les attributs

Dans la pratique, les générateurs sont souvent déclenchés via des attributs. Par exemple, en ajoutant un attribut [AutoToString] à une classe, une méthode ToString() peut être générée automatiquement au moment de la compilation.


// Code utilisateur
[AutoToString]
public partial class Personne
{
    public string Nom { get; set; }
    public int Age { get; set; }
}

// Code généré par le générateur (pendant la compilation)
public partial class Personne
{
    public override string ToString() =>
        $""Personne : Nom={Nom}, Âge={Age}"";
}

Cette approche élimine le boilerplate et facilite la maintenance du code.


Générateur incrémental (C# 10+)

Introduit avec C# 10, le générateur incrémental améliore les performances et prend en charge la compilation incrémentale. Le code est régénéré uniquement pour les fichiers modifiés.


using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public class GenerateurIncrementalSimple : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax,
                transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node)
            .Collect();

        context.RegisterSourceOutput(classes, (spc, classList) =>
        {
            foreach (var cls in classList)
            {
                string nom = cls.Identifier.Text;
                string code = $@"
namespace AutoGen
{{
    public static class {nom}Info
    {{
        public static void Ecrire() =>
            System.Console.WriteLine(""Classe : {nom}"");
    }}
}}";
                spc.AddSource($"{nom}Info.g.cs", SourceText.From(code, Encoding.UTF8));
            }
        });
    }
}

Ce modèle améliore considérablement les performances de compilation dans les grands projets. Les générateurs ne s’exécutent désormais que pour les fichiers affectés.


Source Generator vs Reflection

CaractéristiqueSource GeneratorReflection
Moment d’exécutionTemps de compilationTemps d’exécution
PerformanceÉlevée (code pré-généré)Faible (analyse à l’exécution)
Cas d’utilisationORM, Sérialisation, Client APIPlugins, Tests, Types dynamiques
Visibilité du codeLes fichiers générés sont visibles dans le projetActif uniquement à l’exécution

Performances et bonnes pratiques


Exemple : génération automatique de DTO

Supposons que vous vouliez générer des classes DTO à partir de modèles correspondant à des tables de base de données. Le Source Generator suivant crée une classe {NomDuModèle}Dto pour chaque modèle.


// Classe modèle
[AutoDto]
public class Utilisateur
{
    public int Id { get; set; }
    public string Nom { get; set; }
}

// Code généré
public class UtilisateurDto
{
    public int Id { get; set; }
    public string Nom { get; set; }
}

Cela élimine la nécessité d’écrire les DTO manuellement et synchronise automatiquement les changements du modèle.


TL;DR

  • Les Source Generators sont des outils basés sur Roslyn qui génèrent du code pendant la compilation.
  • Ils n’ont aucun coût d’exécution, contrairement à la réflexion, et sont plus performants et sécurisés.
  • Implémentés via les interfaces ISourceGenerator ou IIncrementalGenerator.
  • Le code généré est inclus dans la compilation et fonctionne comme du code C# classique.
  • Couramment utilisés pour les sérialiseurs, les mappers, les générateurs de DTO ou la génération de clients API.

Articles connexes