Introducción: Por qué tu código debería hablar el lenguaje del negocio
\n\nImagine: está escribiendo la configuración para un robot de trading complejo. En lugar de cientos de líneas de if-else y números mágicos, simplemente escribe: BuyWhen(Price.CrossesAbove(MA(50))). Su jefe, que no sabe C#, mira este código y entiende la lógica. Esto no es magia, es un DSL en C#.
Los lenguajes específicos del dominio (Domain-Specific Languages, DSL) no son solo una tendencia de moda. Son una herramienta que transforma el caos de los detalles técnicos en código elegante y legible. En este artículo no solo hablaremos de teoría: crearemos un DSL real en C# desde cero, analizaremos errores típicos y mostraremos cómo implementar esta práctica en su proyecto hoy mismo.
\n\n¿Qué es un DSL y por qué lo necesita un desarrollador?
\n\nUn DSL es un lenguaje de programación creado para resolver una tarea específica. A diferencia de un lenguaje de propósito general (C#, Java, Python), un DSL habla el lenguaje del dominio. En C# existen dos enfoques principales:
\n\n- \n
- DSL internos — se construyen sobre C# usando Fluent API y Expression Trees. Permanece en su entorno nativo. \n
- DSL externos — requieren análisis sintáctico (a menudo con Roslyn) y generan código C#. \n
Beneficios reales de un DSL: reducción de la carga cognitiva, disminución de la cantidad de errores (la lógica de negocio está separada de la implementación técnica) y aceleración de la incorporación de nuevos desarrolladores.
\n\nCreando un DSL interno en C#: guía paso a paso
\n\nPaso 1: Definir el dominio y la sintaxis
\n\nSupongamos que creamos un DSL para validación de datos. En lugar de:
\n\nif (user.Age < 18 || user.Name == null || user.Email == null)\n throw new ValidationException();\n\nQueremos escribir así:
\n\nValidate.User(user)\n .MustBeAdult()\n .MustHaveName()\n .MustHaveEmail();\n\nPaso 2: Fluent Interface — la base del DSL en C#
\n\nEl elemento clave es devolver this desde cada método. Creamos la clase validadora:
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("El usuario es menor de edad");\n return this;\n }\n\n public UserValidator MustHaveName()\n {\n if (string.IsNullOrWhiteSpace(_user.Name))\n _errors.Add("El nombre es obligatorio");\n return this;\n }\n\n public void Validate()\n {\n if (_errors.Any())\n throw new ValidationException(string.Join("; ", _errors));\n }\n}\n\nY un método de fábrica para una entrada elegante:
\n\npublic static class Validate\n{\n public static UserValidator User(User user) => new UserValidator(user);\n}\n\nPaso 3: Añadir construcciones condicionales y contexto
\n\nUn DSL real en C# debe soportar bifurcaciones. Implementamos When:
public UserValidator When(bool condition, Func<UserValidator, UserValidator> validatorFunc)\n{\n if (condition)\n return validatorFunc(this);\n return this;\n}\n\nUso:
\n\nValidate.User(user)\n .MustHaveName()\n .When(user.Age > 18, v => v.MustBeAdult());\n\nNivel avanzado: DSL con Expression Trees en C#
\n\nPara construir consultas dinámicas (ORM, reglas) usamos Expression<Func<T, bool>>. Creamos un constructor de reglas:
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