1. Introduction
Quand vous écrivez du code, vous vous êtes sûrement surpris parfois à penser : « Pourquoi je copie encore et encore le même modèle de code pour différentes classes ? » Ou : « Pourquoi la sérialisation, le logging, le mapping des données — c'est autant de lignes répétitives ? » Parfois on aimerait que quelqu'un (ou quelque chose) écrive ces modèles pénibles pour vous.
C'est là que les Source Generators entrent en scène — une fonctionnalité de C# apparue avec .NET 5 et qui évolue encore activement. Un Source Generator est une bibliothèque qui s'exécute à la compilation et peut générer dynamiquement du code C# qui est injecté dans votre projet avant la compilation finale.
Pourquoi c'est utile ?
- Automatisation de la routine : Évite d'écrire des classes/méthodes répétitives (boilerplate).
- Sécurité vérifiée par le compilateur : Le code généré est compilé avec le vôtre (contrairement à T4 ou à la réflexion).
- Haute performance : Sérialisation, DI, mapping, etc. sans le coût de la réflexion au runtime.
- Support des patterns modernes : Implémentation d'approches qui étaient difficiles/coûteuses sans génération de code.
Comment les Source Generators fonctionnent « sous le capot » ?
Un Source Generator est une bibliothèque .NET (généralement un projet de type Class Library) qui implémente l'interface ISourceGenerator. Pendant la compilation, Roslyn exécute tous les générateurs branchés et leur donne accès à l'arbre syntaxique de votre code.
Le générateur analyse votre code, décide quoi et où générer, et crée de nouveaux fichiers C# que le compilateur compile immédiatement.
Génération automatique de ToString
Commençons par du simple. Imaginons que nous avons une classe avec beaucoup de propriétés, et il faut implémenter ToString. À la main, ça ressemble à ceci :
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
=> $"Person(Name={Name}, Age={Age})";
}
Mais si les propriétés deviennent très nombreuses — c'est fastidieux, et on peut facilement oublier de mettre à jour quelque chose. Un Source Generator peut le faire pour vous !
2. Comment créer son Source Generator ?
Création du projet
Ouvrez JetBrains Rider ou Visual Studio, créez un nouveau projet de type Class Library (.NET Standard) — ce sont typiquement des projets utilisables pour des générateurs. Ensuite, ajoutez les paquets NuGet :
- Microsoft.CodeAnalysis.CSharp
- Microsoft.CodeAnalysis.Analyzers
Attributs importants
- [Generator] — indique que cette classe est un Source Generator.
Modèle minimal de générateur
Voici un exemple minimal fonctionnel :
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// On peut enregistrer des actions supplémentaires (optionnel)
}
public void Execute(GeneratorExecutionContext context)
{
var code = @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello, world! I am generated!"";
}
}";
context.AddSource("HelloWorldGenerator", SourceText.From(code, Encoding.UTF8));
}
}
Ce générateur simple ajoute toujours une classe statique HelloWorld avec la méthode SayHello pendant la compilation.
Comment utiliser les Source Generators dans l'application principale ?
Référencez le projet générateur comme paquet NuGet ou Project Reference dans la section Analyzer (plus de détails dans la documentation officielle).
Le code généré est immédiatement disponible dans votre projet — rien d'autre à inclure, utilisez simplement :
// Ceci sera généré automatiquement !
using Generated;
Console.WriteLine(HelloWorld.SayHello());
3. Exemple réel : génération automatique de ToString
Supposons que nous voulons que toutes les classes marquées par l'attribut [AutoToString] reçoivent automatiquement une implémentation de la méthode ToString. Pour cela il faudra :
- Créer votre attribut.
- Analyser toutes les classes avec cet attribut.
- Pour chaque classe trouvée générer la méthode ToString.
Attribut
[AttributeUsage(AttributeTargets.Class)]
public class AutoToStringAttribute : Attribute
{
}
Utilisation dans le code
[AutoToString]
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
Logique simple de génération
Le générateur recherchera les classes avec [AutoToString] et générera à peu près ce code :
public override string ToString()
=> $"Product(Name={Name}, Price={Price})";
Extrait réel du code du générateur
L'idée de base — parcourir l'arbre syntaxique avec Roslyn :
public void Execute(GeneratorExecutionContext context)
{
// On analyse tous les arbres syntaxiques
foreach (var tree in context.Compilation.SyntaxTrees)
{
var root = tree.GetRoot();
// On cherche toutes les classes avec l'attribut voulu (exemple simplifié !)
var classes = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Where(c => c.AttributeLists
.SelectMany(al => al.Attributes)
.Any(a => a.Name.ToString().Contains("AutoToString")));
foreach (var @class in classes)
{
var className = @class.Identifier.Text;
// On récupère toutes les propriétés de la classe
var props = @class.Members
.OfType<PropertyDeclarationSyntax>()
.Select(p => p.Identifier.Text)
.ToArray();
var toStringCode = string.Join(", ", props.Select(p => $"{p}={{this.{p}}}"));
var generated = $@"
partial class {className}
{{
public override string ToString() => $""{className}({toStringCode})"";
}}";
context.AddSource($"{className}_ToString", SourceText.From(generated, Encoding.UTF8));
}
}
}
Remarque : pour du code en production on utilise une analyse plus correcte via le SemanticModel de Roslyn.
4. Nuances utiles
À quoi faire attention
Les Source Generators ne peuvent pas modifier le code source existant — ils ne peuvent créer que de nouveaux fichiers (par exemple des classes partial supplémentaires, des méthodes, etc.). Cela signifie que si votre classe est déclarée partial, on peut lui générer des méthodes ou propriétés additionnelles.
Il est parfois difficile de parser correctement la syntaxe et de prendre en compte toutes les subtilités du langage (classes imbriquées, generics, modificateurs, etc.). L'auteur du générateur doit s'assurer que le code généré est compilable et ne casse pas le projet.
Un autre piège — si vous générez des méthodes qui implémentent une interface, assurez-vous que les fichiers sont générés à chaque build. Sinon des erreurs de compilation étranges peuvent apparaître. Les outils modernes résolvent souvent ces problèmes, mais il faut en être conscient.
Source Generators vs réflexion
Réflexion : s'exécute au runtime, coûteuse en ressources, non vérifiée par le compilateur, souvent lente sur de grands volumes de données.
Source Generator : génère du code à la compilation. Tout est vérifié statiquement, l'IDE voit les méthodes, l'auto-complétion fonctionne, et les performances sont celles d'un code C# normal.
Utilité pratique
- System.Text.Json : génération de la sérialisation/désérialisation sans réflexion.
- Conception de conteneurs DI : par exemple Microsoft.Extensions.DependencyInjection avec génération du graphe de dépendances.
- Mappers comme Mapster : passage de la réflexion à la génération de code de mapping à la compilation.
- Frameworks de test : autogénération de méthodes de test basées sur des attributs.
- ASP.NET Minimal APIs (à partir de .NET 7) : génération d'endpoint handlers.
Configuration, paramètres et options
Les générateurs peuvent être configurés via des paramètres MSBuild, des fichiers additionnels et des conventions. Par exemple on peut générer des ToString différents selon l'environnement (Debug/Release) ou la configuration de l'application.
Comment les Source Generators se relient aux tâches réelles
Pour un développeur qui vise du code propre et rapide, c'est un excellent outil : moins de routine, plus de vérifications à la compilation, suggestions de l'IDE et refactorings clairs. La connaissance des générateurs est de plus en plus demandée en entretien — de la sérialisation et DI au mapping.
Cycle de vie d'un Source Generator
| Étape | Ce qui se passe |
|---|---|
| 1. Projet ajouté | Votre générateur est ajouté comme analyzer/reference |
| 2. Roslyn compile la source | Le générateur reçoit l'AST (arbre syntaxique abstrait) |
| 3. Le générateur s'exécute | Ajoute de nouveaux fichiers .cs pour la compilation |
| 4. Tout est compilé | Les fichiers générés deviennent partie de votre assembly |
| 5. Le code est prêt ! | Les méthodes/classes générées sont disponibles pour appel |
5. Débogage et erreurs typiques
Une des erreurs les plus fréquentes chez les débutants qui écrivent des générateurs — oublier le mot-clé partial sur la classe à laquelle vous voulez ajouter du code. Si vous n'ajoutez pas partial, le compilateur ne verra tout simplement pas vos modifications. Parfois le fichier généré peut ne pas apparaître dans l'IDE avant une recompilation — ne paniquez pas.
Faites attention au nommage des fichiers générés : si vous donnez le même Name à tous, ils vont s'écraser. Astuce : insérer le nom de la classe dans le nom de fichier généré, par exemple context.AddSource($"{className}_ToString", ...).
Erreur de double import d'attributs — si vous générez une classe avec un attribut déjà présent dans le projet principal, il y aura un conflit. Il est préférable d'extraire les attributs nécessaires dans un projet commun ou de n'ajouter le code généré que lorsque c'est nécessaire.
GO TO FULL VERSION