Einleitung: Warum Ihr Code die Sprache des Geschäfts sprechen sollte
\n\nStellen 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#.
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\nWas ist eine DSL und warum braucht ein Entwickler sie?
\n\nEine 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
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\nErstellen einer internen DSL in C#: Schritt-für-Schritt-Anleitung
\n\nSchritt 1: Domäne und Syntax definieren
\n\nAngenommen, wir erstellen eine DSL zur Datenvalidierung. Anstelle von:
\n\nif (user.Age < 18 || user.Name == null || user.Email == null)\n throw new ValidationException();\n\nMöchten wir so schreiben:
\n\nValidate.User(user)\n .MustBeAdult()\n .MustHaveName()\n .MustHaveEmail();\n\nSchritt 2: Fluent Interface – die Basis einer DSL in C#
\n\nDas Schlüsselelement ist die Rückgabe von this aus jeder Methode. Wir erstellen eine Validator-Klasse:
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\nUnd eine Factory-Methode für einen eleganten Einstieg:
\n\npublic static class Validate\n{\n public static UserValidator User(User user) => new UserValidator(user);\n}\n\nSchritt 3: Bedingte Konstrukte und Kontext hinzufügen
\n\nEine echte DSL in C# sollte Verzweigungen unterstützen. Wir implementieren When:
public UserValidator When(bool condition, Func<UserValidator, UserValidator> validatorFunc)\n{\n if (condition)\n return validatorFunc(this);\n return this;\n}\n\nVerwendung:
\n\nValidate.User(user)\n .MustHaveName()\n .When(user.Age > 18, v => v.MustBeAdult());\n\nFortgeschrittene Stufe: DSL mit Expression Trees in C#
\n\nZum Erstellen dynamischer Abfragen (ORM, Regeln) verwenden wir Expression<Func<T, bool>>. Wir erstellen einen Regel-Builder:
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