DSL em C#: Criando sua própria linguagem de programação em uma noite

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: Por que seu código deve falar a linguagem do negócio

\n\n

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

\n\n

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

O que é DSL e por que um desenvolvedor precisa dela?

\n\n

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

Criando um DSL interno em C#: guia passo a passo

\n\n

Passo 1: Definindo o domínio e a sintaxe

\n\n

Suponha que estamos criando uma DSL para validação de dados. Em vez de:

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

Queremos escrever assim:

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

Passo 2: Fluent Interface — a base da DSL em C#

\n\n

O elemento-chave é retornar this de cada método. Criamos a classe 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("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\n

E um método de fábrica para uma entrada elegante:

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

Passo 3: Adicionando construções condicionais e contexto

\n\n

Uma DSL real em C# deve suportar ramificações. 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

Nível avançado: DSL com Expression Trees em C#

\n\n

Para construir consultas dinâmicas (ORM, regras), usamos Expression<Func<T, bool>>. Criamos um construtor de regras:

\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