DSL in C#: Erstellen Sie Ihre eigene Programmiersprache an einem Abend

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum Ihr Code die Sprache des Geschäfts sprechen sollte

\n\n

Stellen Sie sich vor: Sie schreiben eine Konfiguration für einen komplexen Handelsroboter. Anstelle von Hunderten Zeilen if-else und magischen Zahlen schreiben Sie einfach: BuyWhen(Price.CrossesAbove(MA(50))). Ihr Manager, der kein C# kennt, sieht sich diesen Code an und versteht die Logik. Das ist keine Magie – das ist DSL in C#.

\n\n

Domänenspezifische Sprachen (Domain-Specific Languages, DSL) sind nicht nur ein modischer Trend. Sie sind ein Werkzeug, das das Chaos technischer Details in eleganten, lesbaren Code verwandelt. In diesem Artikel werden wir nicht nur über Theorie sprechen – wir werden eine echte DSL in C# von Grund auf neu erstellen, typische Fehler analysieren und zeigen, wie Sie diese Praxis noch heute in Ihr Projekt integrieren können.

\n\n

Was ist eine DSL und warum braucht ein Entwickler sie?

\n\n

Eine DSL ist eine Programmiersprache, die zur Lösung einer bestimmten Aufgabe entwickelt wurde. Im Gegensatz zu einer allgemeinen Sprache (C#, Java, Python) spricht eine DSL die Sprache der Domäne. In C# gibt es zwei Hauptansätze:

\n\n
    \n
  • Interne DSLs – werden mit Fluent API und Expression Trees auf C# aufgebaut. Sie bleiben in Ihrer gewohnten Umgebung.
  • \n
  • Externe DSLs – erfordern Parsing (oft über Roslyn) und generieren C#-Code.
  • \n
\n\n

Der tatsächliche Nutzen von DSL: Reduzierung der kognitiven Belastung, Verringerung der Anzahl von Fehlern (Geschäftslogik ist von der technischen Implementierung getrennt) und Beschleunigung des Onboardings neuer Entwickler.

\n\n

Erstellen einer internen DSL in C#: Schritt-für-Schritt-Anleitung

\n\n

Schritt 1: Domäne und Syntax definieren

\n\n

Angenommen, wir erstellen eine DSL zur Datenvalidierung. Anstelle von:

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

Möchten wir so schreiben:

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

Schritt 2: Fluent Interface – die Basis einer DSL in C#

\n\n

Das Schlüsselelement ist die Rückgabe von this aus jeder Methode. Wir erstellen eine Validator-Klasse:

\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("Benutzer ist minderjährig");\n        return this;\n    }\n\n    public UserValidator MustHaveName()\n    {\n        if (string.IsNullOrWhiteSpace(_user.Name))\n            _errors.Add("Name ist erforderlich");\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

Und eine Factory-Methode für einen eleganten Einstieg:

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

Schritt 3: Bedingte Konstrukte und Kontext hinzufügen

\n\n

Eine echte DSL in C# sollte Verzweigungen unterstützen. Wir implementieren 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

Verwendung:

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

Fortgeschrittene Stufe: DSL mit Expression Trees in C#

\n\n

Zum Erstellen dynamischer Abfragen (ORM, Regeln) verwenden wir Expression<Func<T, bool>>. Wir erstellen einen Regel-Builder:

\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

Buchempfehlungen