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 ?
- Pour générer automatiquement du code répétitif difficile à écrire manuellement
- Pour remplacer la réflexion par une solution au moment de la compilation dans des scénarios critiques en performance
- Dans les systèmes basés sur les ORM, les sérialiseurs, les mappers, les proxys, les événements ou les attributs
- Pour générer du code typé de manière sûre, comme les clients JSON ou API
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
- Créez un nouveau projet Class Library (.NET Standard).
- 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éristique | Source Generator | Reflection |
|---|---|---|
| Moment d’exécution | Temps de compilation | Temps d’exécution |
| Performance | Élevée (code pré-généré) | Faible (analyse à l’exécution) |
| Cas d’utilisation | ORM, Sérialisation, Client API | Plugins, Tests, Types dynamiques |
| Visibilité du code | Les fichiers générés sont visibles dans le projet | Actif uniquement à l’exécution |
Performances et bonnes pratiques
- Les générateurs fonctionnent en mode lecture seule : ils ne peuvent pas modifier les fichiers existants, mais seulement ajouter du code nouveau.
- Le code généré apparaît avec l’extension
.g.cssous « Analyzers → Generated Files ». - Évitez les opérations lourdes (ex. accès aux fichiers, I/O) dans le générateur.
- Ne générez pas de code inutile pour ne pas ralentir la compilation.
- Le texte source généré doit être en UTF-8 et ajouté via
context.AddSource().
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
ISourceGeneratorouIIncrementalGenerator. - 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
Analyse de code avec Roslyn Compiler API en C#
Apprenez l’analyse de code en C# avec Roslyn, y compris les arbres syntaxiques et la génération de code.
Génériques en C# (List<T>, Dictionary<TKey,TValue>)
Apprenez les génériques en C# (List<T>, Dictionary<TKey,TValue>) pour écrire un code réutilisable et typé, avec exemples.
Optimisation des performances avec Span<T> et Memory<T> en C#
Apprenez l’optimisation des performances en C# avec Span<T> et Memory<T> pour une gestion mémoire efficace.
Principes SOLID en C#
Application des principes SOLID en C# avec des exemples : pour un code flexible, maintenable et testable.
Réflexion et liaison tardive en C#
Apprenez la réflexion et la liaison tardive en C# pour analyser les types à l’exécution avec des exemples.