Introdução: Por que seu código deve falar a linguagem do negócio
\n\nImagine: você está escrevendo a configuração para um robô de negociação complexo. Em vez de centenas de linhas de if-else e números mágicos, você simplesmente escreve: BuyWhen(Price.CrossesAbove(MA(50))). Seu gerente, que não conhece C#, olha para esse código e entende a lógica. Isso não é magia — é DSL em C#.
As linguagens específicas de domínio (Domain-Specific Languages, DSL) não são apenas uma tendência da moda. São uma ferramenta que transforma o caos dos detalhes técnicos em código elegante e legível. Neste artigo, não vamos apenas falar sobre teoria — vamos criar uma DSL real em C# do zero, analisar erros típicos e mostrar como implementar essa prática no seu projeto ainda hoje.
\n\nO que é DSL e por que um desenvolvedor precisa dela?
\n\nDSL é uma linguagem de programação criada para resolver uma tarefa específica. Ao contrário de uma linguagem geral (C#, Java, Python), a DSL fala a linguagem do domínio. Em C#, existem duas abordagens principais:
\n\n- \n
- DSL internos — construídos sobre o C# usando Fluent API e Expression Trees. Você permanece no ambiente nativo. \n
- DSL externos — exigem parsing (frequentemente via Roslyn) e geram código C#. \n
Benefícios reais da DSL: redução da carga cognitiva, diminuição do número de bugs (a lógica de negócio é separada da implementação técnica) e aceleração da integração de novos desenvolvedores.
\n\nCriando um DSL interno em C#: guia passo a passo
\n\nPasso 1: Definindo o domínio e a sintaxe
\n\nSuponha que estamos criando uma DSL para validação de dados. Em vez de:
\n\nif (user.Age < 18 || user.Name == null || user.Email == null)\n throw new ValidationException();\n\nQueremos escrever assim:
\n\nValidate.User(user)\n .MustBeAdult()\n .MustHaveName()\n .MustHaveEmail();\n\nPasso 2: Fluent Interface — a base da DSL em C#
\n\nO elemento-chave é retornar this de cada método. Criamos a classe 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("Usuário menor de idade");\n return this;\n }\n\n public UserValidator MustHaveName()\n {\n if (string.IsNullOrWhiteSpace(_user.Name))\n _errors.Add("Nome é obrigatório");\n return this;\n }\n\n public void Validate()\n {\n if (_errors.Any())\n throw new ValidationException(string.Join("; ", _errors));\n }\n}\n\nE um método de fábrica para uma entrada elegante:
\n\npublic static class Validate\n{\n public static UserValidator User(User user) => new UserValidator(user);\n}\n\nPasso 3: Adicionando construções condicionais e contexto
\n\nUma DSL real em C# deve suportar ramificações. 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\nNível avançado: DSL com Expression Trees em C#
\n\nPara construir consultas dinâmicas (ORM, regras), usamos Expression<Func<T, bool>>. Criamos um construtor de regras:
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