[CS] CARLOS SEIDL EN
Artigos

Records em C#: menos código repetitivo, mais significado!

Records em C# para Value Objects, DTOs e mensagens: imutabilidade, igualdade por valor, with e pattern matching.

Por 4 min de leitura
  • C#
  • Records
  • DDD
Capa do artigo: Records em C#

Por que usar records em C# para reduzir boilerplate

Em muitas bases de código .NET, grande parte do esforço é gasta criando tipos de dados simples (DTOs, Value Objects, mensagens de eventos) com getters, setters, Equals, GetHashCode e construtores repetitivos. Records em C# reduzem esse boilerplate e tornam a intenção do domínio mais explícita, especialmente quando a imutabilidade é uma regra do jogo.

Neste artigo—dando continuidade à nossa série e mantendo o mesmo padrão visual e textual—vamos focar no uso prático de records para construir APIs mais limpas, modelos mais seguros e domínios mais expressivos, preparando o terreno para integrações, testes e evolução arquitetural.


Conceitos de records: imutabilidade e igualdade por valor

  • Record: tipo de referência com igualdade por valor (por padrão), ideal para representar dados imutáveis e significativos no domínio.
  • Imutabilidade: estados não mudam após criação; atualizações acontecem via with (cópia não destrutiva).
  • Record class vs record struct:
    • record class: por referência, igualdade por valor (componentes).
    • record struct: por valor, útil para cenários de alto desempenho e stack-friendly.
  • Positional records: sintaxe concisa para definir propriedades no cabeçalho do tipo.
  • Igualdade por valor: dois records com mesmos componentes são iguais, independentemente de referência.
  • Deconstruct: suporte nativo para desconstrução de componentes.
  • Compatibilidade arquitetural: encaixa bem em Domain-Driven Design (Value Objects), DTOs e contratos de mensagens.

Onde usar records em uma arquitetura em camadas

Em uma arquitetura em camadas (API → Application → Domain → Infrastructure), os records brilham em:

  • Domain: modelagem de Value Objects imutáveis (e.g., Email, Money, Document), com validações e sem identidade própria.
  • Application: DTOs e Commands/Queries (CQRS) com igualdade por valor, simplificando testes e mapping.
  • API: contratos de entrada/saída (request/response) concisos, fáceis de serializar.
  • Infrastructure: mapeamento para persistência/integrações mantendo as invariantes do domínio.

O fluxo típico: a API recebe um DTO (record), a camada Application orquestra casos de uso e transforma DTOs em Value Objects (records), que são utilizados pelas entidades/serviços do domínio. 


Records em código: Value Objects, DTOs, with e pattern matching

5.1. Value Object de Domínio com record

Pontos-chave

  • Imutável: sem set;. O estado é definido no construtor.
  • Invariantes no factory (Create): garante validade desde a criação.
  • Igualdade por valor: dois Email com o mesmo Address são iguais sem sobrescrever manualmente Equals/GetHashCode.

5.2. DTO de Request/Response com record (Positional)

Pontos-chave

  • Sintaxe concisa para reduzir boilerplate.
  • record struct para Money: econômico e sem alocação extra quando passado por valor (cenário ilustrativo).

5.3. Uso de with e pattern matching


5.4. Integração com a Camada de Aplicação

Pontos-chave

  • Conversão explícita de DTO → Value Object assegura invariantes.
  • O contrato de resposta permanece conciso e expressivo.

5.5. Considerações de Serialização (System.Text.Json)

Observação: records positional e nominal funcionam bem com System.Text.Json quando possuem accessors públicos de leitura; ajuste constructors explícitos se você precisar de parse customizado.


Boas práticas e cuidados ao usar records

  • Use records para dados e significado, não para comportamentos ricos. Comportamentos do domínio pertencem a Entidades/Serviços; records são ótimos para Value Objects e contratos.
  • Construa invariantes na criação (factory estática ou construtor privado + método estático): evita instâncias inválidas.
  • Cuidado com mutabilidade acidental: evite init exposto quando a semântica exigir verdadeira imutabilidade.
  • Versionamento de contratos: ao evoluir records usados em APIs/eventos, prefira novos tipos (CustomerCreatedV2) a alterações breaking.
  • Interoperabilidade com EF Core: para Value Objects, mapeie como owned types; garanta construtores compatíveis com a infraestrutura quando necessário (por ex., construtor privado + configuration).
  • Perf: record struct pode reduzir alocações em cenários hot-path; valide com benchmarks antes de adotar.

Quando usar records e quando preferir classes

Use quando

  • Modelar Value Objects (ex.: Email, CPF, Money).
  • Definir DTOs, Commands/Queries e event payloads.
  • Priorizar imutabilidade e igualdade por valor para clareza e testabilidade.

Evite quando

  • O tipo exige identidade e ciclo de vida (entidades ricas): prefira class.
  • Há forte dependência de ORMs com requisitos de setters públicos ou proxies (mitigável com owned types/configuração).
  • O domínio demanda mutações frequentes e intrínsecas ao tipo (considere classes tradicionais).

Records em C#: menos boilerplate, mais intenção

Records em C# reduzem código repetitivo e deixam explícita a intenção do domínio, oferecendo imutabilidade, igualdade por valor e sintaxe enxuta. Em arquiteturas em camadas, eles tornam DTOs e Value Objects mais expressivos, simplificam testes e favorecem contratos claros entre componentes. Aplique-os onde há dados com semântica forte e invariantes bem definidas; reserve classes tradicionais para entidades e cenários com identidade e mutabilidade inerentes.

Leia também

Precisa de APIs .NET de alta disponibilidade? Conheça o serviço de backend e arquitetura .NET.

← todos os artigos