DSL en C#: Creamos nuestro propio lenguaje de programación en una tarde

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué tu código debería hablar el lenguaje del negocio

\n\n

Imagine: 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#.

\n\n

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\n

Un 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
\n\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\n

Creando un DSL interno en C#: guía paso a paso

\n\n

Paso 1: Definir el dominio y la sintaxis

\n\n

Supongamos que creamos un DSL para validación de datos. En lugar de:

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

Queremos escribir así:

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

Paso 2: Fluent Interface — la base del DSL en C#

\n\n

El elemento clave es devolver this desde cada método. Creamos la clase validadora:

\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("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\n

Y un método de fábrica para una entrada elegante:

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

Paso 3: Añadir construcciones condicionales y contexto

\n\n

Un DSL real en C# debe soportar bifurcaciones. Implementamos 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

Uso:

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

Nivel avanzado: DSL con Expression Trees en C#

\n\n

Para construir consultas dinámicas (ORM, reglas) usamos Expression<Func<T, bool>>. Creamos un constructor de reglas:

\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