DSL en C# : Créer votre propre langage de programmation en une soirée

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : Pourquoi votre code devrait parler le langage des affaires

\n\n

Imaginez : vous écrivez la configuration d'un robot de trading complexe. Au lieu de centaines de lignes de if-else et de nombres magiques, vous écrivez simplement : BuyWhen(Price.CrossesAbove(MA(50))). Votre manager, qui ne connaît pas le C#, regarde ce code et comprend la logique. Ce n'est pas de la magie, c'est un DSL en C#.

\n\n

Les langages dédiés (Domain-Specific Languages, DSL) ne sont pas qu'une simple tendance. C'est un outil qui transforme le chaos des détails techniques en un code élégant et lisible. Dans cet article, nous ne nous contenterons pas de parler de théorie : nous allons créer un vrai DSL en C# à partir de zéro, analyser les erreurs typiques et montrer comment intégrer cette pratique dans votre projet dès aujourd'hui.

\n\n

Qu'est-ce qu'un DSL et pourquoi un développeur en a-t-il besoin ?

\n\n

Un DSL est un langage de programmation créé pour résoudre une tâche spécifique. Contrairement à un langage général (C#, Java, Python), un DSL parle le langage du domaine métier. En C#, il existe deux approches principales :

\n\n
    \n
  • DSL internes — construits par-dessus C# à l'aide de l'API Fluent et des arbres d'expression. Vous restez dans votre environnement natif.
  • \n
  • DSL externes — nécessitent une analyse syntaxique (souvent via Roslyn) et génèrent du code C#.
  • \n
\n\n

Les avantages concrets d'un DSL : réduction de la charge cognitive, diminution du nombre de bugs (la logique métier est séparée de l'implémentation technique) et accélération de l'intégration des nouveaux développeurs.

\n\n

Créer un DSL interne en C# : guide étape par étape

\n\n

Étape 1 : Définir le domaine et la syntaxe

\n\n

Supposons que nous créions un DSL pour la validation de données. Au lieu de :

\n\n
if (user.Age < 18 || user.Name == null || user.Email == null)\n    throw new ValidationException();
\n\n

Nous voulons écrire :

\n\n
Validate.User(user)\n    .MustBeAdult()\n    .MustHaveName()\n    .MustHaveEmail();
\n\n

Étape 2 : Interface Fluent — la base d'un DSL en C#

\n\n

L'élément clé est le retour de this depuis chaque méthode. Créons une classe de validation :

\n\n
public class UserValidator\n{\n    private readonly User _user;\n    private readonly List<string> _errors = new();\n\n    public UserValidator(User user) => _user = user;\n\n    public UserValidator MustBeAdult()\n    {\n        if (_user.Age < 18)\n            _errors.Add("L'utilisateur est mineur");\n        return this;\n    }\n\n    public UserValidator MustHaveName()\n    {\n        if (string.IsNullOrWhiteSpace(_user.Name))\n            _errors.Add("Le nom est obligatoire");\n        return this;\n    }\n\n    public void Validate()\n    {\n        if (_errors.Any())\n            throw new ValidationException(string.Join("; ", _errors));\n    }\n}
\n\n

Et une méthode factory pour une entrée élégante :

\n\n
public static class Validate\n{\n    public static UserValidator User(User user) => new UserValidator(user);\n}
\n\n

Étape 3 : Ajouter des constructions conditionnelles et du contexte

\n\n

Un vrai DSL en C# doit prendre en charge les branchements. Implémentons When :

\n\n
public UserValidator When(bool condition, Func<UserValidator, UserValidator> validatorFunc)\n{\n    if (condition)\n        return validatorFunc(this);\n    return this;\n}
\n\n

Utilisation :

\n\n
Validate.User(user)\n    .MustHaveName()\n    .When(user.Age > 18, v => v.MustBeAdult());
\n\n

Niveau avancé : DSL avec les arbres d'expression en C#

\n\n

Pour construire des requêtes dynamiques (ORM, règles), utilisons Expression<Func<T, bool>>. Créons un constructeur de règles :

\n\n
public class RuleBuilder<T>\n{\n    private Expression<Func<T, bool>> _expression;\n\n    public RuleBuilder(Expression<Func<T, bool>> expression) => _expression = expression;\n\n    public RuleBuilder<T> And(Expression<Func<T, bool>> other)\n    {\n        var andExpression = Ex                

Blogs

Book Recommendations